How to detect edges in an image with OpenCV

Edges mark sharp intensity changes that often become the first boundary signal in a vision pipeline. In OpenCV, Canny edge detection turns a photo or generated scene into a binary edge map before contour finding, shape measurement, or visual inspection.

Canny works on a single-channel image, so the script loads the source as grayscale, applies a small Gaussian blur to reduce isolated noise, and passes two hysteresis thresholds to cv.Canny(). Pixels connected to strong gradients remain white in the output, while background and weak unconnected texture stay black.

The sample path uses input/scene.png and writes output/edges.png. The edge-pixel percentage gives a quick sanity check because a near-zero value usually means the thresholds are too high, while a crowded edge map usually means the thresholds are too low or the image needs more blur.

Steps to detect image edges with OpenCV:

  1. Place the source image at input/scene.png.

    Use an image with visible object boundaries or scene structure. Canny works on grayscale data, so color is not required unless later project code needs it.

  2. Create detect_edges.py.
    detect_edges.py
    #!/usr/bin/env python3
    import argparse
    from pathlib import Path
     
    import cv2 as cv
    import numpy as np
     
     
    def odd_kernel(value: str) -> int:
        kernel = int(value)
        if kernel < 1 or kernel % 2 == 0:
            raise argparse.ArgumentTypeError("blur kernel must be a positive odd integer")
        return kernel
     
     
    parser = argparse.ArgumentParser(description="Detect image edges with OpenCV Canny.")
    parser.add_argument("input_image", type=Path)
    parser.add_argument("output_image", type=Path)
    parser.add_argument("--low", type=float, default=80.0)
    parser.add_argument("--high", type=float, default=160.0)
    parser.add_argument("--blur", type=odd_kernel, default=5)
    parser.add_argument("--aperture", type=odd_kernel, default=3)
    parser.add_argument("--l2-gradient", action="store_true")
    args = parser.parse_args()
     
    if args.aperture not in {3, 5, 7}:
        raise SystemExit("aperture must be 3, 5, or 7")
    if args.low >= args.high:
        raise SystemExit("--low must be lower than --high")
     
    gray = cv.imread(str(args.input_image), cv.IMREAD_GRAYSCALE)
    if gray is None:
        raise SystemExit(f"could not read image: {args.input_image}")
     
    source = cv.GaussianBlur(gray, (args.blur, args.blur), 0)
    edges = cv.Canny(
        source,
        args.low,
        args.high,
        apertureSize=args.aperture,
        L2gradient=args.l2_gradient,
    )
     
    args.output_image.parent.mkdir(parents=True, exist_ok=True)
    if not cv.imwrite(str(args.output_image), edges):
        raise SystemExit(f"could not write image: {args.output_image}")
     
    edge_pixels = int(np.count_nonzero(edges))
    total_pixels = edges.size
    edge_percent = edge_pixels / total_pixels * 100
     
    print(f"input: {args.input_image}")
    print(f"image size: {gray.shape[1]}x{gray.shape[0]}")
    print(f"thresholds: {args.low:.0f}/{args.high:.0f}")
    print(f"blur kernel: {args.blur}x{args.blur}")
    print(f"aperture: {args.aperture}")
    print(f"edge pixels: {edge_pixels} ({edge_percent:.2f}%)")
    print(f"output: {args.output_image}")
  3. Run Canny edge detection and write the edge map.
    $ python3 detect_edges.py input/scene.png output/edges.png
    input: input/scene.png
    image size: 720x480
    thresholds: 80/160
    blur kernel: 5x5
    aperture: 3
    edge pixels: 4968 (1.44%)
    output: output/edges.png

    --low and --high set the hysteresis thresholds. Raise both values when texture becomes edges, or lower them when object boundaries disappear.

  4. Verify the saved edge map can be reopened.
    $ python3 -c 'import cv2 as cv, numpy as np; edges = cv.imread("output/edges.png", cv.IMREAD_GRAYSCALE); print(edges.shape); print(np.unique(edges))'
    (480, 720)
    [  0 255]

    The shape confirms the saved image height and width. The two pixel values show a binary edge map where white pixels are retained edges.

  5. Remove detect_edges.py after moving the edge detector into project code.
    $ rm detect_edges.py