04 OpenCV 4.13.0 算法流水线 本章面向需要把“单个 API 示例”组装成可验证视觉系统的开发者。内容以 OpenCV 4.13.0 的公开 API 和源码头文件为准,统一按“输入 → 处理 → 输出 → 失败诊断 → 参数调整 → 验证”描述。示例默认使用 Python 接口 cv2;C++ 中对应类型通常为 cv::Mat、cv::Point2f、cv::KeyPoint 等。
本章只讨论算法流水线。模块边界、构建方式、HAL 优化和完整源码目录分别由其他章节负责。
4.1 流水线设计与输入契约 否 是 采集或解码 "输入有效?" 记录并拒绝处理 颜色/位深/尺寸归一化 主算法 几何或语义后处理 定量验证 结果与中间产物
4.1.1 类型、范围与坐标
数据
常见类型
数值范围
典型用途
彩色图
uint8, H×W×3,BGR
[0,255]
显示、传统视觉
灰度图
uint8, H×W
[0,255]
阈值、边缘、角点
浮点图
float32, H×W 或 H×W×C
常见 [0,1],也可无界
卷积、梯度、DNN
二值掩膜
uint8, H×W
推荐 {0,255}
形态学、轮廓、逻辑运算
标签图
int32, H×W
0...N,特殊算法可有负值
连通域、Watershed
点集
float32/float64, N×2 或 N×1×2
像素坐标
几何估计
OpenCV 图像坐标原点在左上角,x 向右、y 向下;数组索引则写作 image[y, x]。裁剪 ROI 后得到的坐标相对 ROI 原点,回写原图必须加偏移。几何估计宜使用浮点坐标,标签和类别掩膜的缩放必须使用 INTER_NEAREST。
最小输入守卫:
1 2 3 4 5 6 7 8 import cv2 as cvimport numpy as npimage = cv.imread("input.png" , cv.IMREAD_COLOR) if image is None or image.size == 0 : raise ValueError("图像读取失败或为空" ) if image.dtype != np.uint8 or image.ndim != 3 or image.shape[2 ] != 3 : raise TypeError(f"期望 uint8 BGR,实际为 {image.dtype} , {image.shape} " )
4.1.2 可复现验证 每一级至少记录输入类型、关键参数、有效像素比例、候选数、内点率、误差分位数及耗时。调参时先固定数据、随机种子、尺寸和指标,再确认颜色、位深、范围与坐标映射;从前往后一次只调一层,先提高召回率,最后优化速度。不要只保存最终叠加图,中间掩膜、边缘、内点和重投影结果更能定位失败阶段。
4.2 图像预处理 BGR/灰度原图 裁剪 ROI cvtColor resize convertTo/normalize 连续且语义明确的输入
4.2.1 API、参数和输入输出
cv.cvtColor(src, code):COLOR_BGR2GRAY、COLOR_BGR2HSV、COLOR_BGR2Lab 等;输出通道数由转换码决定。
cv.resize(src, dsize, fx, fy, interpolation):缩小优先 INTER_AREA,普通放大优先 INTER_LINEAR,离散标签使用 INTER_NEAREST。
cv.normalize(src, dst, alpha, beta, norm_type):可做 NORM_MINMAX 或范数归一化,不等同于神经网络的均值方差标准化。
cv.copyMakeBorder:补边方式包括 BORDER_CONSTANT、REPLICATE、REFLECT_101。
cv.remap(src, map1, map2, interpolation):输入映射表常为 CV_32FC1/2 或转换后的定点格式,输出尺寸由映射表决定。
保持宽高比缩放并补边时,设原尺寸为 (W,H)、目标尺寸为 (W_t,H_t):
[ s=\min(W_t/W,\ H_t/H),\quad W’=sW,\quad H’=sH ]
左右和上下总补边分别是 W_t-W'、H_t-H'。后续坐标映射必须执行 x=(x_t-p_x)/s、y=(y_t-p_y)/s。
1 2 3 4 5 6 7 8 9 10 11 def letterbox (bgr, size=(640 , 640 ), value=(114 , 114 , 114 ) ): tw, th = size h, w = bgr.shape[:2 ] scale = min (tw / w, th / h) nw, nh = round (w * scale), round (h * scale) resized = cv.resize(bgr, (nw, nh), interpolation=cv.INTER_LINEAR) left, top = (tw - nw) // 2 , (th - nh) // 2 right, bottom = tw - nw - left, th - nh - top out = cv.copyMakeBorder(resized, top, bottom, left, right, cv.BORDER_CONSTANT, value=value) return out, scale, (left, top)
4.2.2 失败、调参和验证
失败原因:把 RGB 模型输入当 BGR;整数除法导致归一化全零;宽高顺序写反;ROI 越界;重复压缩造成细节丢失。
调参顺序:先定颜色空间和范围,再定目标尺寸及插值,最后选择补边和归一化。
验证方法:检查通道均值、直方图、角点坐标回映射误差;对掩膜确认缩放后类别集合没有新增值。
4.3 滤波、增强与二值化 灰度或彩色图 噪声模型判断 Gaussian/Median/Bilateral CLAHE/直方图均衡 固定/Otsu/自适应阈值 uint8 二值掩膜
4.3.1 滤波与增强
GaussianBlur(src, ksize, sigmaX, sigmaY=0):核尺寸通常为正奇数;适合近似高斯噪声。
medianBlur(src, ksize):ksize 为大于 1 的奇数;对椒盐噪声稳健。
bilateralFilter(src, d, sigmaColor, sigmaSpace):同时按空间距离和颜色差加权,保边但较慢。
equalizeHist(src):只接受 8 位单通道图;彩色图宜仅均衡亮度通道。
createCLAHE(clipLimit, tileGridSize):限制局部对比度,降低全局均衡放大噪声的风险。
高斯滤波可写为:
[ G(x,y)=\frac{1}{2\pi\sigma^2}\exp\left(-\frac{x^2+y^2}{2\sigma^2}\right),\quad I’=G*I ]
双边滤波的权重同时包含空间项与强度项:
[ w(p,q)\propto \exp(-|p-q|^2/2\sigma_s^2)\exp(-|I_p-I_q|^2/2\sigma_r^2) ]
4.3.2 二值化
threshold(src, thresh, maxval, type):输入通常为单通道;返回实际阈值和输出图。
THRESH_OTSU 自动寻找类间方差最大的阈值,应与 THRESH_BINARY 或反相模式组合。
adaptiveThreshold 要求 8 位单通道输入;blockSize 必须为大于 1 的奇数,C 从局部均值或高斯加权均值中扣除。
目标比背景暗时,优先考虑 THRESH_BINARY_INV,避免后续把背景当作前景。
Otsu 最大化类间方差:
[ \sigma_b^2(t)=\omega_0(t)\omega_1(t)\left[\mu_0(t)-\mu_1(t)\right]^2 ]
1 2 3 4 5 6 7 gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) gray = cv.GaussianBlur(gray, (5 , 5 ), 0 ) clahe = cv.createCLAHE(clipLimit=2.0 , tileGridSize=(8 , 8 )) enhanced = clahe.apply(gray) otsu_t, mask = cv.threshold( enhanced, 0 , 255 , cv.THRESH_BINARY + cv.THRESH_OTSU )
4.3.3 失败、调参和验证
失败原因:光照梯度破坏全局阈值双峰假设;核过大吞掉细线;CLAHE 放大纹理和噪声;前景极少时 Otsu 偏向背景。
调参顺序:先决定灰度/亮度通道和前景极性;再按噪声选滤波器;随后调增强;最后调阈值、blockSize 和 C。
验证方法:画灰度直方图;统计前景比例;在标注集上计算 Precision、Recall、IoU;用不同曝光和噪声级别做扰动测试。
4.4 形态学处理 白点 黑洞 收缩/分离 扩张/连接 二值图/灰度图 getStructuringElement "缺陷类型" Opening Closing Erode Dilate 清理后掩膜
结构元素可由 getStructuringElement(MORPH_RECT/MORPH_ELLIPSE/MORPH_CROSS, ksize) 构造。erode、dilate 和 morphologyEx 接受灰度或多通道图,但语义最清晰的输入通常是 {0,255} 二值图;输出尺寸和类型默认不变。
集合意义下,腐蚀与膨胀为:
[ A\ominus B={z\mid B_z\subseteq A},\qquad A\oplus B={z\mid (\hat B)_z\cap A\neq\varnothing} ]
开运算是 (A⊖B)⊕B,闭运算是 (A⊕B)⊖B。MORPH_GRADIENT 近似膨胀减腐蚀,MORPH_TOPHAT 提取比结构元素小的亮结构,MORPH_BLACKHAT 提取暗结构。
1 2 3 kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5 , 5 )) opened = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel, iterations=1 ) clean = cv.morphologyEx(opened, cv.MORPH_CLOSE, kernel, iterations=2 )
失败原因:结构元素大于目标最窄部分;迭代过多合并相邻目标;前景极性颠倒;边界补值引入伪结构。
调参顺序:先确认白色代表前景,再按缺陷方向选操作,随后选形状,再从小核、单次迭代开始。
验证方法:比较连通域数量、面积分布、孔洞数和细线保留率;用异或图显示处理前后改变的像素。
4.5 Canny 与梯度 uint8 灰度图 平滑 Sobel/Scharr 梯度 幅值与方向 非极大值抑制 双阈值与滞后连接 单像素边缘
4.5.1 梯度 API 与原理 Sobel(src, ddepth, dx, dy, ksize, scale, delta) 输出导数图;对 8 位输入常用 CV_16S 或 CV_32F,不能直接用 CV_8U 保存带符号梯度。Scharr 使用固定高精度 3×3 核,适合小核一阶导。magnitude 和 phase 可由 Gx/Gy 得到幅值与方向。
[ G=\sqrt{G_x^2+G_y^2},\qquad \theta=\operatorname{atan2}(G_y,G_x) ]
Canny(image, threshold1, threshold2, apertureSize=3, L2gradient=False) 接受 8 位图;低阈值以下丢弃,高阈值以上保留,中间像素仅在连接强边缘时保留。L2gradient=False 使用 |Gx|+|Gy| 近似幅值,设为 True 使用欧氏幅值。
1 2 3 4 5 6 gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) blur = cv.GaussianBlur(gray, (5 , 5 ), 1.2 ) gx = cv.Scharr(blur, cv.CV_32F, 1 , 0 ) gy = cv.Scharr(blur, cv.CV_32F, 0 , 1 ) mag = cv.magnitude(gx, gy) edges = cv.Canny(blur, 50 , 150 , apertureSize=3 , L2gradient=True )
4.5.2 失败、调参和验证
失败原因:低阈值太低导致纹理泛滥;高阈值太高造成断边;平滑过强令弱边缘消失;图像动态范围变化使固定阈值失效。
调参顺序:先控制输入尺度和降噪,再看梯度幅值分布,先调高阈值定位强边,再调低阈值连接,最后考虑形态学闭合。
验证方法:边缘像素比例、与标注边缘的容差匹配 F1、轮廓闭合率;同时检查 Gx、Gy,避免只看最终 Canny 图。
4.6 轮廓与形状分析 单通道二值图 findContours 层级/面积/周长过滤 approxPolyDP/convexHull 矩/外接框/圆/椭圆 形状与几何量
4.6.1 API 和类型
findContours(image, mode, method):输入为 8 位单通道二值图;RETR_EXTERNAL 仅取外轮廓,RETR_TREE 建立完整嵌套层级。
CHAIN_APPROX_SIMPLE 压缩水平、垂直和斜线共线点;需要全部边界点时用 CHAIN_APPROX_NONE。
contourArea(contour, oriented=False) 返回面积;arcLength(contour, closed) 返回周长。
approxPolyDP(curve, epsilon, closed) 使用 Douglas–Peucker 近似;常设 epsilon=k×周长。
boundingRect 返回轴对齐整数框;minAreaRect 返回旋转矩形;fitEllipse 至少需要 5 个点。
moments 给出空间矩;当 m00 != 0 时质心为 (m10/m00, m01/m00)。
圆度常定义为:
[ C=\frac{4\pi A}{P^2} ]
理想圆接近 1;像素化和轮廓噪声会使其下降。矩形填充率可取 A/(w·h),凸度可取 A/A_hull。
1 2 3 4 5 6 7 8 9 10 11 contours, hierarchy = cv.findContours( mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE ) shapes = [] for cnt in contours: area = cv.contourArea(cnt) if area < 200 : continue perimeter = cv.arcLength(cnt, True ) polygon = cv.approxPolyDP(cnt, 0.02 * perimeter, True ) shapes.append((cnt, polygon, area))
4.6.2 失败、调参和验证
失败原因:轮廓断裂;噪声产生大量小轮廓;ROI 偏移未还原;用像素面积固定阈值处理多分辨率数据;忽略层级导致孔洞误计。
调参顺序:先改善掩膜闭合性,再选检索模式,然后按相对面积过滤,最后调近似误差和形状指标。
验证方法:把轮廓、顶点编号和层级叠加到原图;检查面积守恒;对旋转、缩放样本验证归一化指标稳定性。
4.7 几何变换与文档扫描 文档图像 灰度/边缘/形态学 四边形候选 四角点稳定排序 getPerspectiveTransform warpPerspective 光照校正/二值化 正视文档
4.7.1 变换模型 仿射变换用 2×3 矩阵,保持平行关系;透视变换用单应矩阵:
[ s\begin{bmatrix}x’\y’\1\end{bmatrix} =H\begin{bmatrix}x\y\1\end{bmatrix},\quad H\in\mathbb{R}^{3\times3} ]
getAffineTransform 需要三对点,getPerspectiveTransform 需要四对对应点。warpAffine/warpPerspective 的 dsize 是输出 (width,height)。perspectiveTransform 用于变换点集,不用于重采样整幅图。remap 适合相机去畸变或任意稠密映射。
四点排序不能仅在所有视角下机械依赖坐标和差;更稳妥的做法是先取凸包、验证四边形非自交,再按质心极角排序并统一起点和方向。
1 2 3 4 5 6 7 8 src = np.array([[120 , 80 ], [920 , 110 ], [890 , 680 ], [90 , 650 ]], dtype=np.float32) dst = np.array([[0 , 0 ], [799 , 0 ], [799 , 599 ], [0 , 599 ]], dtype=np.float32) H = cv.getPerspectiveTransform(src, dst) scan = cv.warpPerspective(image, H, (800 , 600 ), flags=cv.INTER_LINEAR, borderMode=cv.BORDER_REPLICATE)
4.7.2 失败、调参和验证
失败原因:四角顺序不一致导致翻转;候选不是凸四边形;文档边缘出画;镜头畸变未校正;纸张弯曲却使用平面单应。
调参顺序:先去畸变,再提高边缘召回,按面积/凸性/角度筛四边形,最后确定输出比例与插值。
验证方法:将目标四角通过逆单应投回原图;计算边线直线度、相邻边夹角、文字行水平度和正反向变换误差。
4.8 特征匹配与几何验证 图像 A detectAndCompute 图像 B detectAndCompute KNN 匹配 Lowe ratio/互检 RANSAC/USAC H/F/E 与内点掩膜 重投影或极线验证
4.8.1 描述子与匹配器 SIFT_create 生成 CV_32F 浮点描述子,常用 L2 距离;ORB_create 生成 CV_8U 二进制描述子,使用 NORM_HAMMING。BFMatcher.knnMatch(k=2) 可做比率测试;crossCheck=True 只适用于一一最近邻匹配,不与 KNN 比率测试同时使用。
Lowe 比率判据为:
[ d_1 < r d_2 ]
其中 d1,d2 是最近与次近距离,r 常从 0.7~0.8 起试。它只衡量描述子歧义,不能代替几何验证。
平面场景或纯旋转:findHomography(..., RANSAC/USAC_MAGSAC, ransacReprojThreshold)。
未标定一般双视图:findFundamentalMat,满足 x_2^T F x_1=0。
已知相机内参:findEssentialMat 与 recoverPose,满足 x_2^T E x_1=0。
1 2 3 4 5 6 7 8 9 10 11 12 13 gray1 = cv.cvtColor(image1, cv.COLOR_BGR2GRAY) gray2 = cv.cvtColor(image2, cv.COLOR_BGR2GRAY) orb = cv.ORB_create(nfeatures=2000 ) kp1, des1 = orb.detectAndCompute(gray1, None ) kp2, des2 = orb.detectAndCompute(gray2, None ) if des1 is None or des2 is None : raise RuntimeError("描述子为空" ) knn = cv.BFMatcher(cv.NORM_HAMMING).knnMatch(des1, des2, k=2 ) good = [m for pair in knn if len (pair) == 2 for m, n in [pair] if m.distance < 0.75 * n.distance] pts1 = np.float32([kp1[m.queryIdx].pt for m in good]) pts2 = np.float32([kp2[m.trainIdx].pt for m in good]) H, inliers = cv.findHomography(pts1, pts2, cv.USAC_MAGSAC, 3.0 )
4.8.2 失败、调参和验证
失败原因:纹理重复;关键点过少或集中在小区域;运动模糊;误用距离范数;动态物体主导;平面模型用于强视差场景。
调参顺序:先确认描述子类型与范数,再增加特征覆盖;随后调比率阈值;最后按图像噪声调 RANSAC 阈值和置信度。
验证方法:报告原始匹配数、筛后匹配数、内点数和内点率;画内点空间分布;计算对称传输误差或极线距离的中位数和 95 分位数。
4.9 相机标定、PnP 与双目 4.9.1 单目标定 多姿态标定图 棋盘/圆点/ChArUco 检测 亚像素优化 calibrateCamera 逐帧重投影误差 K、D、图像尺寸
针孔模型为:
[ s\begin{bmatrix}u\v\1\end{bmatrix} =K[R|t]\begin{bmatrix}X\Y\Z\1\end{bmatrix},\quad K=\begin{bmatrix}f_x&0&c_x\0&f_y&c_y\0&0&1\end{bmatrix} ]
findChessboardCorners 的 patternSize 是每行、每列的内角点数,不是方格数。cornerSubPix 输入灰度图和初始角点。calibrateCamera 输入每帧 N×3 物点与对应 N×2 像点,输出 RMS、内参、畸变、每帧外参。鱼眼模型应使用 cv.fisheye API,不能混用普通畸变系数语义。
1 2 3 4 5 6 7 8 9 ok, corners = cv.findChessboardCorners(gray, (9 , 6 )) if ok: term = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_COUNT, 30 , 1e-3 ) corners = cv.cornerSubPix(gray, corners, (11 , 11 ), (-1 , -1 ), term) image_points.append(corners) object_points.append(board_points.copy()) rms, K, dist, rvecs, tvecs = cv.calibrateCamera( object_points, image_points, gray.shape[::-1 ], None , None )
失败原因包括姿态单一、标定板仅居中、覆盖深度不足、运动模糊、打印比例不准和运行分辨率变化。调参先改善采集覆盖,再剔除检测错误帧,最后谨慎固定高阶畸变参数。验证时必须用 projectPoints 计算逐帧误差,而不只看总体 RMS,并检查 fx/fy、主点及畸变曲线是否合理。
4.9.2 PnP 位姿 N 个 3D 物点 solvePnP/Ransac 对应 2D 像点 + K,D rvec,tvec projectPoints 重投影误差/坐标轴
solvePnP 的物点和像点必须一一对应。通用情况可从 SOLVEPNP_ITERATIVE 开始;平面方形标记可考虑 SOLVEPNP_IPPE_SQUARE,其四个物点有规定顺序;存在离群点时使用 solvePnPRansac,再以内点调用 solvePnPRefineLM。
失败原因:单位不一致;点顺序错误;点近共线;平面解歧义;错误内参;把相机到物体变换与物体到相机变换混淆。调参先核对坐标系和单位,再选求解器,再调 RANSAC 重投影阈值,最后细化。验证应检查正深度、重投影误差以及时序位姿连续性。
4.9.3 双目标定与深度 同步左右图 stereoCalibrate stereoRectify initUndistortRectifyMap/remap StereoBM/SGBM disparity reprojectImageTo3D
校正后对应点应位于同一水平扫描线。若焦距为 f、基线为 B、视差为 d,则:
[ Z=\frac{fB}{d} ]
StereoSGBM_create 的 numDisparities 必须为 16 的倍数,blockSize 为正奇数;常用平滑罚项起点为 P1=8·C·b^2、P2=32·C·b^2。输出视差通常带 4 位小数定标,转浮点时除以 16。
失败原因:左右不同步、曝光差、垂直视差、弱纹理、重复纹理和遮挡。调参先验证校正,再定最小/最大视差范围,然后调窗口与 P1/P2,最后做左右一致性和 speckle 清理。验证用极线纵向误差、有效视差比例和已知距离平面的深度误差。
4.10 光流、背景建模与跟踪 4.10.1 光流 前一灰度帧 LK/Farneback 当前灰度帧 前帧特征点 位移/稠密 flow 状态过滤与重检测
亮度恒常和小运动假设给出光流约束:
[ I_xu+I_yv+I_t=0 ]
Lucas–Kanade 在局部窗口内联合求解,calcOpticalFlowPyrLK 输入前后帧及 N×1×2 float32 点,输出新点、status 和误差。winSize 控制局部窗口,maxLevel 控制金字塔层数。calcOpticalFlowFarneback 输出 H×W×2 float32 稠密流。
1 2 3 4 5 6 7 8 p0 = cv.goodFeaturesToTrack(prev_gray, 500 , 0.01 , 8 ) p1, status, err = cv.calcOpticalFlowPyrLK( prev_gray, gray, p0, None , winSize=(21 , 21 ), maxLevel=3 , criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 30 , 0.01 ) ) good0 = p0[status.ravel() == 1 ] good1 = p1[status.ravel() == 1 ]
失败原因:快速运动超出金字塔搜索;遮挡;无纹理区域;曝光突变;点漂移。调参先改善帧间隔和角点质量,再增大金字塔层级,随后调窗口,最后加前后向一致性并周期性重检测。验证可将点正向追踪后反向追踪,统计往返误差和有效点寿命。
4.10.2 背景建模 视频帧 MOG2/KNN apply 前景掩膜 去阴影/形态学 连通域/检测框
createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True) 适合较稳定相机;阴影通常标为 127,前景为 255,不能直接把所有非零值视为目标。learningRate=0 冻结模型,负值让算法自动选择,较大值适应更快但会吞掉慢速目标。
失败原因:相机抖动、周期背景、自动曝光、目标长时间静止。调参先固定相机和曝光,再设置学习率与历史长度,然后调判定阈值,最后清理阴影和小区域。验证使用前景 IoU、误警面积、目标进入/离开后的适应时间。
4.10.3 状态跟踪 Kalman Filter 只估计状态,不负责目标检测或多目标关联。线性模型为:
[ x_k=Ax_{k-1}+Bu_k+w_k,\qquad z_k=Hx_k+v_k ]
cv.KalmanFilter(dynamParams, measureParams) 的 transitionMatrix、measurementMatrix、过程噪声 Q、测量噪声 R 必须与状态定义一致。失败通常来自时间步长错误、噪声协方差不合理和关联错配。先验证检测,再验证关联门限,随后调 R,最后调 Q;以轨迹 RMSE、ID 切换、丢失恢复时间验证。
4.11 图像分割 连通区域 接触目标 交互抠图 种子扩张 图像/二值先验 "任务" connectedComponentsWithStats distanceTransform + watershed grabCut floodFill 标签/统计量
4.11.1 连通域与 Watershed connectedComponentsWithStats 接受 8 位单通道图,返回 int32 标签、包围框/面积统计和质心。connectivity=4 不连接对角像素,8 会连接。
Watershed 把梯度图看作地形,从 marker 指定的种子扩张。watershed(image, markers) 的图像是 8 位三通道,marker 是 int32 单通道;处理后分水岭边界标为 -1。未知区域必须为 0,已知前景实例使用不同正整数标签。
1 2 3 4 5 6 7 8 9 dist = cv.distanceTransform(mask, cv.DIST_L2, 5 ) _, sure_fg = cv.threshold(dist, 0.45 * dist.max (), 255 , 0 ) sure_fg = sure_fg.astype(np.uint8) sure_bg = cv.dilate(mask, np.ones((3 , 3 ), np.uint8), iterations=3 ) unknown = cv.subtract(sure_bg, sure_fg) count, markers = cv.connectedComponents(sure_fg) markers = markers + 1 markers[unknown == 255 ] = 0 markers = cv.watershed(image.copy(), markers.astype(np.int32))
4.11.2 GrabCut、Flood Fill 与聚类 grabCut 输入 BGR 图、uint8 掩膜、矩形、背景/前景模型和迭代数;掩膜值是 GC_BGD/FGD/PR_BGD/PR_FGD。矩形初始化要求目标大体位于框内,精细边界应再用掩膜初始化。floodFill 从种子按颜色差扩张,需谨慎选择 loDiff/upDiff。颜色聚类可把像素转为 float32 N×C 后调用 kmeans,但空间不连续且类别编号无语义。
失败原因:marker 粘连或缺失导致 Watershed 欠分/过分;GrabCut 框包含太多背景或目标触边;聚类只看颜色忽略空间。
调参顺序:先确认任务线索,再优化前景/背景种子;Watershed 调距离阈值,GrabCut 调初始化和迭代,最后做区域过滤。
验证方法:IoU、Dice、边界 F1、实例计数误差;将每个标签随机着色,检查标签泄漏和碎片。
4.12 图像拼接 有重叠的多图 特征与成对匹配 相机/单应估计 Bundle Adjustment 投影与曝光补偿 接缝搜索 羽化/多频段融合 全景图
cv.Stitcher.create(mode) 提供完整封装:PANORAMA 假设相机旋转为主,SCANS 更适合仿射扫描件。stitch(images) 返回状态码和结果,不应只捕获异常。状态包括成功、需要更多图像、单应估计失败和相机参数调整失败。
高级流水线位于 cv::detail:特征寻找、最佳匹配图、运动估计、Bundle Adjustment、球面/柱面 warper、曝光补偿、接缝寻找及 blender 可独立替换。多频段融合减少低频曝光缝,羽化融合开销较小但不能修复错位。
1 2 3 4 stitcher = cv.Stitcher.create(cv.Stitcher_PANORAMA) status, panorama = stitcher.stitch(images) if status != cv.Stitcher_OK: raise RuntimeError(f"拼接失败,状态码={status} " )
失败原因:重叠不足;纹理单一;视差显著;滚动快门;移动物体;输入顺序或尺度跨度过大。
调参顺序:先检查相邻图重叠和匹配内点,再选 PANORAMA/SCANS;随后调匹配置信度与投影模型,最后才调曝光、接缝和融合。
验证方法:匹配图连通性、边缘重影宽度、接缝两侧亮度差、直线弯曲程度和有效画布比例;不要仅凭“能输出图”判定成功。
4.13 DNN 推理 readNet/ONNX Net 原图 resize/letterbox blobFromImage setInput forward 按模型解码 阈值/NMS 坐标回映射
4.13.1 输入、执行和输出 readNet/readNetFromONNX 返回 cv.dnn.Net。blobFromImage(image, scalefactor, size, mean, swapRB, crop, ddepth) 通常输出 NCHW 四维 blob。实际预处理由模型训练配置决定,OpenCV 不会自动推断 letterbox、均值、标准差或输出布局。
常见标准化为:
[ x’=(x-\mu)\cdot s ]
注意 blobFromImage 的顺序是先减 mean 再乘 scalefactor;swapRB=True 会交换红蓝通道。若模型要求逐通道标准差,通常需自行构造浮点输入。
1 2 3 4 5 6 7 8 net = cv.dnn.readNetFromONNX("model.onnx" ) inp, scale, (pad_x, pad_y) = letterbox(image, (640 , 640 )) blob = cv.dnn.blobFromImage( inp, scalefactor=1.0 / 255.0 , size=(640 , 640 ), mean=(0 , 0 , 0 ), swapRB=True , crop=False ) net.setInput(blob) outputs = net.forward(net.getUnconnectedOutLayersNames())
setPreferableBackend 和 setPreferableTarget 只表达偏好;部署前应通过构建信息、运行日志和基准确认实际后端。动态形状、量化模型和自定义算子必须做兼容性测试。
4.13.2 后处理、失败与验证 检测后处理通常先按置信度筛选,再调用 NMSBoxes(bboxes, scores, score_threshold, nms_threshold)。IoU 为:
[ \operatorname{IoU}(A,B)=\frac{|A\cap B|}{|A\cup B|} ]
失败原因:BGR/RGB 错;拉伸与 letterbox 混用;输出维度按错误模型版本解释;置信度定义错误;坐标忘记减 padding;类别索引偏移;NMS 跨类别误抑制。
调参顺序:先用已知输入对齐训练框架的预处理和原始张量,再实现解码与坐标回映射,随后调置信度和 NMS,最后选择后端和批量。验证应比较参考框架逐层或最终输出、固定样本数值误差、mAP/IoU,以及预热后的端到端延迟和峰值内存。
4.14 模板、霍夫与 ArUco 4.14.1 模板匹配 搜索图 matchTemplate 模板 响应图 minMaxLoc/局部峰值 阈值与 NMS
matchTemplate(image, templ, method, mask) 输出尺寸为 (W-w+1, H-h+1) 的浮点响应图。TM_SQDIFF 系列越小越好,相关系数系列越大越好。归一化相关系数可理解为对均值和能量归一化后的相似度。
1 2 3 4 response = cv.matchTemplate(gray, templ, cv.TM_CCOEFF_NORMED) _, score, _, location = cv.minMaxLoc(response) if score >= 0.85 : x, y = location
失败原因是尺度、旋转、透视和光照差异,以及低纹理模板。先固定尺度搜索,再设置分数阈值和峰值抑制,最后才扩展图像金字塔或角度搜索。用正负样本分数分布、定位误差和重复检测率验证。
4.14.2 霍夫直线与圆 灰度图 Canny HoughLinesP/HoughCircles 长度/角度/半径筛选 几何对象
直线法式为:
[ \rho=x\cos\theta+y\sin\theta ]
HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap) 输入 8 位二值边缘图,输出线段端点。HoughCircles 常用 HOUGH_GRADIENT;dp 是累加器与图像分辨率反比,minDist 抑制相邻圆,param1 通常是内部 Canny 高阈值,param2 是圆心累加阈值。
失败原因:边缘太碎、纹理伪线、maxLineGap 连接无关边、半径范围过宽。调参先稳定边缘,再限定 ROI 和角度/半径范围,随后调投票阈值,最后调线长或圆间距。验证用端点到标注线距离、角度误差、圆心误差和半径误差。
4.14.3 ArUco 与 ChArUco 去畸变或原始图 ArucoDetector.detectMarkers 角点 + IDs + rejected 板级匹配/角点细化 solvePnP projectPoints/drawFrameAxes
OpenCV 4.13.0 使用 cv.aruco.ArucoDetector(dictionary, detectorParams) 检测标记,输出每个标记的四角、ID 和拒绝候选。字典必须与打印标记一致。姿态估计可将已知标记或 Board 的三维角点与检测二维角点交给 solvePnP;单个方形标记适合 IPPE 方形模型。
1 2 3 4 5 6 dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250) params = cv.aruco.DetectorParameters() detector = cv.aruco.ArucoDetector(dictionary, params) corners, ids, rejected = detector.detectMarkers(image) if ids is not None : cv.aruco.drawDetectedMarkers(image, corners, ids)
失败原因:字典错误;打印边框不足;标记像素过少;反光、模糊或遮挡;角点顺序与三维点不一致;未考虑镜头畸变。调参先保证打印质量和像素尺寸,再选角点细化方式,随后调整自适应阈值窗口和候选周长范围,最后估计位姿。验证 marker 检出率、ID 混淆、角点重投影误差、姿态抖动和不同距离下的稳定性。
4.15 验收与源码核验 验收至少覆盖:输入类型契约;中间结果留存;独立验证集阈值;重投影、极线或逆变换误差;空图与无候选分支;噪声、模糊、曝光、尺度、旋转、遮挡扰动;视频延迟和丢帧;DNN 与参考框架数值对齐。
本章 API 与主要参数已按仓库内 opencv-4.13.0/modules/ 下的 imgproc、features2d、calib3d、video、stitching、dnn、objdetect 公开头文件核验,其中 ArUco 类声明位于 objdetect/include/opencv2/objdetect/aruco_detector.hpp。版本升级时仍须复查函数重载、枚举、默认参数、模型导入兼容性和 Python 绑定。
源码/API 核验不能替代任务数据上的定量验证;只有指标、失败样本和中间产物都可复现,流水线才算完成。