☰
【Qt + OpenCV 工业视觉平台实战】(五) 高级处理 Tab:直方图均衡、GrabCut、分水岭
2026/11/6 1:59:55 网站建设 项目流程

高级处理 Tab 的三个算子都是"组合拳"型算法——不再是单个函数,而是多个基础操作串成的完整方案。


一、功能一览

算子参数一句话原理
直方图均衡化模式(彩色Y通道/灰度)用累积分布函数拉伸灰度分布
GrabCut前景分割边距、迭代次数图割+GMM颜色建模
分水岭分割前景阈值、距离阈值比例距离变换造标记 + 分水岭淹没

源码:src/operators/AdvancedOps.cpp。

二、直方图均衡化:一次"分布整形"

均衡化的本质:把原图的灰度直方图通过累积分布函数(CDF)映射成尽量均匀的分布——暗的地方整体拉开,亮的地方整体压缩,总对比度提升。

单通道直接equalizeHist没什么好说,有意思的是彩色图怎么办:对 B/G/R 三通道分别均衡会产生严重偏色(三个通道独立拉伸,颜色比例被破坏)。正确姿势是转到YCrCb 空间只均衡亮度通道 Y:

Mat ycrcb;cvtColor(input,ycrcb,COLOR_BGR2YCrCb);std::vector<Mat>chs;split(ycrcb,chs);equalizeHist(chs[0],chs[0]);// 只动亮度 Ymerge(chs,ycrcb);cvtColor(ycrcb,out,COLOR_YCrCb2BGR);

参数面板可切换"彩色均衡(Y通道)/灰度均衡",对比效果会发现彩色模式的颜色保持得非常好。想看"均衡到底做了什么",切到图像处理 Tab 的"直方图对比",黄色柱(均衡后)就是答案。

三、GrabCut:以矩形为先验的交互式分割

GrabCut 的玩法:用户框一个矩形 → 框外 definitively 背景、框内可能是前景 → 用 GMM 对前景/背景分别建颜色模型 → 图割(Graph Cut)求最优分割 → 迭代 refine。OpenCV 里grabCut一行搞定,但工程细节都在框外:

Matprocess(constMat&input,constQVariantMap&params,QString*err,QString*)constoverride{if(input.empty()){*err=QStringLiteral("请先打开图像");return{};}if(input.channels()!=3){*err=QStringLiteral("需要彩色图像(BGR)");return{};}// 超过 1500px 自动降采样加速constdoublescale=(std::max)(input.rows,input.cols)>1500?1500.0/(std::max)(input.rows,input.cols):1.0;Mat work;if(scale<1.0)resize(input,work,Size(),scale,scale,INTER_AREA);elseinput.copyTo(work);constintm=qBound(1,vInt(params,QStringLiteral("margin"),25),45);constRectrect(work.cols*m/100,work.rows*m/100,work.cols*(100-2*m)/100,work.rows*(100-2*m)/100);Matmask(work.size(),CV_8UC1,Scalar(GC_BGD));Mat bgd,fgd;grabCut(work,mask,rect,bgd,fgd,qBound(1,vInt(params,QStringLiteral("iters"),5),10),GC_INIT_WITH_RECT);...}

三个工程点:

  1. 大图自动降采样:GrabCut 是迭代算法,2000px 的图能算到你怀疑死机。超过 1500px 先缩到 1500,算完再把结果放大回原尺寸(掩膜用INTER_NEAREST避免插值出半透明边界)
  2. 初始矩形由"边距%"参数生成:本平台没有鼠标框选交互,用中央矩形+可调边距模拟"用户框选"。边距越小框越大,包含的背景越多,分割越难
  3. 掩膜后处理:(mask == GC_FGD) | (mask == GC_PR_FGD)生成前景掩膜,copyTo(out, mask)抠出前景——背景显示为黑

迭代次数 1~10:一般 5 次就收敛,再加大纯属烧时间。

四、分水岭:完整流程

分水岭直接用会过分割(每个噪声小丘都成一片流域),所以经典用法是一整套预处理造"标记"。标准流程:

① 阈值二值化(亮目标为前景,可OTSU自动/可反转) ② 开运算去噪 ③ 距离变换 → 前景核心(sure foreground) ④ 原前景 - 核心 = 未知区域 ⑤ connectedComponents 编号: 背景=1、核心=2..N,未知区=0 ⑥ watershed 淹没 → 边界处标记为 -1

完整代码:

Mat gray=MatUtils::toGray(input);// 二值化(亮目标为前景) + 开运算去噪Mat bin;if(vInt(params,QStringLiteral("autothr"))==1)threshold(gray,bin,0,255,THRESH_BINARY|THRESH_OTSU);elsethreshold(gray,bin,vInt(params,QStringLiteral("thresh"),127),255,THRESH_BINARY);if(vBool(params,QStringLiteral("invert")))bitwise_not(bin,bin);morphologyEx(bin,bin,MORPH_OPEN,getStructuringElement(MORPH_ELLIPSE,Size(3,3)));// 前景为空 → 无法分割, 提示调参if(countNonZero(bin)==0){*logInfo=QStringLiteral("阈值化后无前景像素: 请调整阈值/勾选反转/改用OTSU");returnMatUtils::toColor(input);}// 距离变换 + 确定前景核心// 注意: distanceTransform 输出 CV_32F, normalize 必须显式指定 CV_8U,// 否则后面 subtract(bin CV_8U, sureFg CV_32F) 类型不匹配抛异常Mat dist;distanceTransform(bin,dist,DIST_L2,3);normalize(dist,dist,0,255,NORM_MINMAX,CV_8U);Mat sureFg;threshold(dist,sureFg,vDbl(params,QStringLiteral("distfrac"),0.5)*255.0,255,THRESH_BINARY);// 标记(关键!): connectedComponents 得到 背景0/核心1..N,// 整体+1 → 背景1/核心2..N —— 背景必须有种子, 否则分水岭// 把全部未知区域淹没给唯一种子, 不会产生任何 -1 分界线;// 未知区(bin且非核心)设回 0Mat unknown;subtract(bin,sureFg,unknown);Mat markers;constintnLabels=connectedComponents(sureFg,markers,8,CV_32S);constintnCores=nLabels-1;// 0 号是 sureFg 的背景markers+=1;markers.setTo(Scalar(0),unknown);if(nCores==0){*logInfo=QStringLiteral("距离阈值过大: 无前景核心, 请调小距离阈值比例");returnMatUtils::toColor(input);}Mat color=MatUtils::toColor(input);watershed(color,markers);Mat out=color.clone();out.setTo(Scalar(0,0,255),markers==-1);*logInfo=QStringLiteral("前景核心 %1 个%2").arg(nCores).arg(nCores==1?QStringLiteral("(目标粘连或单目标, 区域间边界可能不可见)"):QString());

关键理解:

  • 距离变换:每个前景点到最近背景的距离。物体中心距离大、边缘距离小 → 阈值化后得到"确定是物体核心"的区域
  • 标记规则:watershed的标记图约定0 = 未知区域、正数 = 种子。背景必须显式给一个种子编号——如果背景全是 0(未知),分水岭会把整张图淹没给唯一种子,markers == -1的分界线一条都不会出现,输出看起来和原图一模一样。正确做法是用connectedComponents编号后整体 +1:背景=1、核心=2…N,未知区设回 0
  • 类型陷阱:distanceTransform输出 CV_32F,normalize要显式指定CV_8U,否则后面subtract(bin CV_8U, sureFg CV_32F)类型不匹配直接抛 OpenCV 异常
  • 两种调参失败要有兜底提示:阈值化后无前景、距离阈值过大无核心,日志都会给出调整方向,而不是默默返回原图
  • "距离阈值比例"参数(0.1~0.9)控制核心收缩程度:调大→核心小→区域分得开但可能漏;调小→核心大→可能粘连

用深色背景+两个相邻亮圆的图测试(内置演示图的绿方块与蓝圆也可以),边界以红色描出,日志显示"前景核心 * 个"。

五、三个算子的共同启示

  1. 高级算法 = 基础操作的正确组合:分水岭 = 阈值+开运算+距离变换+连通域+分水岭,每一步都来自前面 Tab
  2. 交互不足时用参数模拟:GrabCut 的框选、分水岭的种子点,都用滑动条参数近似,保留调参空间
  3. 性能预算要主动管理:降采样、迭代上限

下一篇进入视频 Tab——帧率、实时性、有状态算子。


系列目录:(一) 总体架构 | (二) 基础操作 | (三) 图像处理 | (四) 特征检测 | (五) 高级处理(本篇) | (六) 视频 | (七) 机器学习 | (八) 项目实战 | (九) 性能优化 | (十) 扩展与收官

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询