How to match a template with OpenCV

Template matching in OpenCV compares a small image patch against every possible position in a larger image. It fits controlled image checks where the target icon, marker, part, or UI element keeps the same scale and orientation and the application needs pixel coordinates instead of a trained detector.

In Python, cv2.matchTemplate() creates a one-channel score map and cv2.minMaxLoc() chooses the strongest location. Correlation methods such as TM_CCOEFF_NORMED and TM_CCORR_NORMED use the highest score, while TM_SQDIFF_NORMED uses the lowest score.

A source image and template image are enough when the template is not larger than the source. For a flat color patch, sqdiff-normed avoids the zero-variance ambiguity that can affect correlation methods, and the annotated PNG confirms the chosen rectangle after OpenCV writes it.

Steps to match a template with OpenCV:

  1. Place the source image and template image under input.

    Use input/scene.png and input/template.png. The template must have the same scale and orientation as the target region and must not be larger than the scene image.

  2. Create match_template.py.
    match_template.py
    #!/usr/bin/env python3
    import argparse
    from pathlib import Path
     
    import cv2
     
     
    METHODS = {
        "ccoeff-normed": cv2.TM_CCOEFF_NORMED,
        "ccorr-normed": cv2.TM_CCORR_NORMED,
        "sqdiff-normed": cv2.TM_SQDIFF_NORMED,
    }
     
     
    parser = argparse.ArgumentParser(description="Locate a template image inside a larger image with OpenCV.")
    parser.add_argument("scene", type=Path)
    parser.add_argument("template", type=Path)
    parser.add_argument("output", type=Path)
    parser.add_argument("--method", choices=METHODS, default="ccoeff-normed")
    args = parser.parse_args()
     
    scene_color = cv2.imread(str(args.scene), cv2.IMREAD_COLOR)
    scene_gray = cv2.imread(str(args.scene), cv2.IMREAD_GRAYSCALE)
    template_gray = cv2.imread(str(args.template), cv2.IMREAD_GRAYSCALE)
     
    if scene_color is None or scene_gray is None:
        raise SystemExit(f"could not read scene image: {args.scene}")
    if template_gray is None:
        raise SystemExit(f"could not read template image: {args.template}")
    if template_gray.shape[0] > scene_gray.shape[0] or template_gray.shape[1] > scene_gray.shape[1]:
        raise SystemExit("template image must not be larger than the scene image")
     
    method = METHODS[args.method]
    result = cv2.matchTemplate(scene_gray, template_gray, method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
     
    if method == cv2.TM_SQDIFF_NORMED:
        top_left = min_loc
        score = min_val
        better = "lower"
    else:
        top_left = max_loc
        score = max_val
        better = "higher"
     
    height, width = template_gray.shape
    bottom_right = (top_left[0] + width, top_left[1] + height)
    cv2.rectangle(scene_color, top_left, bottom_right, (0, 0, 255), 3)
    cv2.putText(
        scene_color,
        f"{args.method} {score:.3f}",
        (top_left[0], max(25, top_left[1] - 10)),
        cv2.FONT_HERSHEY_SIMPLEX,
        0.7,
        (0, 0, 255),
        2,
    )
     
    args.output.parent.mkdir(parents=True, exist_ok=True)
    if not cv2.imwrite(str(args.output), scene_color):
        raise SystemExit(f"could not write output image: {args.output}")
     
    print(f"method: {args.method} ({better} score is better)")
    print(f"scene size: {scene_gray.shape[1]}x{scene_gray.shape[0]}")
    print(f"template size: {width}x{height}")
    print(f"result map: {result.shape[1]}x{result.shape[0]}")
    print(f"best score: {score:.3f}")
    print(f"top-left: x={top_left[0]}, y={top_left[1]}")
    print(f"bottom-right: x={bottom_right[0]}, y={bottom_right[1]}")
    print(f"output: {args.output}")
  3. Run the template match with sqdiff-normed.
    $ python3 match_template.py input/scene.png input/template.png output/template-match.png --method sqdiff-normed
    method: sqdiff-normed (lower score is better)
    scene size: 720x480
    template size: 155x130
    result map: 566x351
    best score: 0.000
    top-left: x=60, y=70
    bottom-right: x=215, y=200
    output: output/template-match.png

    Use sqdiff-normed when a lower score should represent a closer pixel difference. Use ccoeff-normed or ccorr-normed for textured templates where a stronger correlation should produce a higher score.

  4. Confirm that OpenCV can read the annotated result.
    $ python3 -c 'import cv2; image = cv2.imread("output/template-match.png"); print(f"annotated image: {image.shape[1]}x{image.shape[0]} channels={image.shape[2]}")'
    annotated image: 720x480 channels=3