#!/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}")