04 OpenCV 4.13.0 算法流水线

04 OpenCV 4.13.0 算法流水线

本章面向需要把“单个 API 示例”组装成可验证视觉系统的开发者。内容以 OpenCV 4.13.0 的公开 API 和源码头文件为准,统一按“输入 → 处理 → 输出 → 失败诊断 → 参数调整 → 验证”描述。示例默认使用 Python 接口 cv2;C++ 中对应类型通常为 cv::Matcv::Point2fcv::KeyPoint 等。

本章只讨论算法流水线。模块边界、构建方式、HAL 优化和完整源码目录分别由其他章节负责。

4.1 流水线设计与输入契约

采集或解码"输入有效?"记录并拒绝处理颜色/位深/尺寸归一化主算法几何或语义后处理定量验证结果与中间产物

4.1.1 类型、范围与坐标

数据 常见类型 数值范围 典型用途
彩色图 uint8, H×W×3,BGR [0,255] 显示、传统视觉
灰度图 uint8, H×W [0,255] 阈值、边缘、角点
浮点图 float32, H×WH×W×C 常见 [0,1],也可无界 卷积、梯度、DNN
二值掩膜 uint8, H×W 推荐 {0,255} 形态学、轮廓、逻辑运算
标签图 int32, H×W 0...N,特殊算法可有负值 连通域、Watershed
点集 float32/float64, N×2N×1×2 像素坐标 几何估计

OpenCV 图像坐标原点在左上角,x 向右、y 向下;数组索引则写作 image[y, x]。裁剪 ROI 后得到的坐标相对 ROI 原点,回写原图必须加偏移。几何估计宜使用浮点坐标,标签和类别掩膜的缩放必须使用 INTER_NEAREST

最小输入守卫:

1
2
3
4
5
6
7
8
import cv2 as cv
import numpy as np

image = 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/灰度原图裁剪 ROIcvtColorresizeconvertTo/normalize连续且语义明确的输入

4.2.1 API、参数和输入输出

  • cv.cvtColor(src, code)COLOR_BGR2GRAYCOLOR_BGR2HSVCOLOR_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_CONSTANTREPLICATEREFLECT_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)/sy=(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/BilateralCLAHE/直方图均衡固定/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 偏向背景。
  • 调参顺序:先决定灰度/亮度通道和前景极性;再按噪声选滤波器;随后调增强;最后调阈值、blockSizeC
  • 验证方法:画灰度直方图;统计前景比例;在标注集上计算 Precision、Recall、IoU;用不同曝光和噪声级别做扰动测试。

4.4 形态学处理

白点黑洞收缩/分离扩张/连接二值图/灰度图getStructuringElement"缺陷类型"OpeningClosingErodeDilate清理后掩膜

结构元素可由 getStructuringElement(MORPH_RECT/MORPH_ELLIPSE/MORPH_CROSS, ksize) 构造。erodedilatemorphologyEx 接受灰度或多通道图,但语义最清晰的输入通常是 {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)⊖BMORPH_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_16SCV_32F,不能直接用 CV_8U 保存带符号梯度。Scharr 使用固定高精度 3×3 核,适合小核一阶导。magnitudephase 可由 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、轮廓闭合率;同时检查 GxGy,避免只看最终 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 几何变换与文档扫描

文档图像灰度/边缘/形态学四边形候选四角点稳定排序getPerspectiveTransformwarpPerspective光照校正/二值化正视文档

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/warpPerspectivedsize 是输出 (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 特征匹配与几何验证

图像 AdetectAndCompute图像 BdetectAndComputeKNN 匹配Lowe ratio/互检RANSAC/USACH/F/E 与内点掩膜重投影或极线验证

4.8.1 描述子与匹配器

SIFT_create 生成 CV_32F 浮点描述子,常用 L2 距离;ORB_create 生成 CV_8U 二进制描述子,使用 NORM_HAMMINGBFMatcher.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
  • 已知相机内参:findEssentialMatrecoverPose,满足 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}
]

findChessboardCornerspatternSize 是每行、每列的内角点数,不是方格数。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,Drvec,tvecprojectPoints重投影误差/坐标轴

solvePnP 的物点和像点必须一一对应。通用情况可从 SOLVEPNP_ITERATIVE 开始;平面方形标记可考虑 SOLVEPNP_IPPE_SQUARE,其四个物点有规定顺序;存在离群点时使用 solvePnPRansac,再以内点调用 solvePnPRefineLM

失败原因:单位不一致;点顺序错误;点近共线;平面解歧义;错误内参;把相机到物体变换与物体到相机变换混淆。调参先核对坐标系和单位,再选求解器,再调 RANSAC 重投影阈值,最后细化。验证应检查正深度、重投影误差以及时序位姿连续性。

4.9.3 双目标定与深度

同步左右图stereoCalibratestereoRectifyinitUndistortRectifyMap/remapStereoBM/SGBMdisparityreprojectImageTo3D

校正后对应点应位于同一水平扫描线。若焦距为 f、基线为 B、视差为 d,则:

[
Z=\frac{fB}{d}
]

StereoSGBM_createnumDisparities 必须为 16 的倍数,blockSize 为正奇数;常用平滑罚项起点为 P1=8·C·b^2P2=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)transitionMatrixmeasurementMatrix、过程噪声 Q、测量噪声 R 必须与状态定义一致。失败通常来自时间步长错误、噪声协方差不合理和关联错配。先验证检测,再验证关联门限,随后调 R,最后调 Q;以轨迹 RMSE、ID 切换、丢失恢复时间验证。

4.11 图像分割

连通区域接触目标交互抠图种子扩张图像/二值先验"任务"connectedComponentsWithStatsdistanceTransform + watershedgrabCutfloodFill标签/统计量

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/ONNXNet原图resize/letterboxblobFromImagesetInputforward按模型解码阈值/NMS坐标回映射

4.13.1 输入、执行和输出

readNet/readNetFromONNX 返回 cv.dnn.NetblobFromImage(image, scalefactor, size, mean, swapRB, crop, ddepth) 通常输出 NCHW 四维 blob。实际预处理由模型训练配置决定,OpenCV 不会自动推断 letterbox、均值、标准差或输出布局。

常见标准化为:

[
x’=(x-\mu)\cdot s
]

注意 blobFromImage 的顺序是先减 mean 再乘 scalefactorswapRB=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())

setPreferableBackendsetPreferableTarget 只表达偏好;部署前应通过构建信息、运行日志和基准确认实际后端。动态形状、量化模型和自定义算子必须做兼容性测试。

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 霍夫直线与圆

灰度图CannyHoughLinesP/HoughCircles长度/角度/半径筛选几何对象

直线法式为:

[
\rho=x\cos\theta+y\sin\theta
]

HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap) 输入 8 位二值边缘图,输出线段端点。HoughCircles 常用 HOUGH_GRADIENTdp 是累加器与图像分辨率反比,minDist 抑制相邻圆,param1 通常是内部 Canny 高阈值,param2 是圆心累加阈值。

失败原因:边缘太碎、纹理伪线、maxLineGap 连接无关边、半径范围过宽。调参先稳定边缘,再限定 ROI 和角度/半径范围,随后调投票阈值,最后调线长或圆间距。验证用端点到标注线距离、角度误差、圆心误差和半径误差。

4.14.3 ArUco 与 ChArUco

去畸变或原始图ArucoDetector.detectMarkers角点 + IDs + rejected板级匹配/角点细化solvePnPprojectPoints/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/ 下的 imgprocfeatures2dcalib3dvideostitchingdnnobjdetect 公开头文件核验,其中 ArUco 类声明位于 objdetect/include/opencv2/objdetect/aruco_detector.hpp。版本升级时仍须复查函数重载、枚举、默认参数、模型导入兼容性和 Python 绑定。

源码/API 核验不能替代任务数据上的定量验证;只有指标、失败样本和中间产物都可复现,流水线才算完成。

文章互动

阅读 --

留言

0 条留言

正在加载留言…