4. 图像处理基础:图像滤波与增强、边缘检测、轮廓提取、点云预处理

各位同学,欢迎来到第四讲。说实话,这一章的内容是视觉引导抓取的“地基”。你想想看,如果相机拍回来的图像全是噪点,或者边缘模糊得像毛玻璃,那后面的识别和抓取根本无从谈起。我个人习惯把这一章叫做“视觉清洗工坊”——我们要把原始数据里乱七八糟的东西过滤掉,只留下最干净、最有用的信息。

4.1 图像滤波与增强

先聊聊滤波。为什么要滤波?说白了,就是去噪。工业相机在产线上拍到的图像,经常会有传感器噪声、光照不均、甚至灰尘带来的伪影。我最早做项目时,就吃过这个亏——一个螺丝钉的轮廓被噪点淹没了,结果机器人抓了个空。

常用的滤波方法,我列个表给你看:

滤波类型 核心思想 适用场景 我踩过的坑
均值滤波 邻域像素取平均 高斯噪声,速度要求高 边缘会变模糊,慎用
高斯滤波 加权平均,中心权重高 通用去噪,保留边缘 核大小选不对,要么没效果要么太糊
中值滤波 取邻域中值 椒盐噪声,极佳 计算慢,实时性差
双边滤波 考虑空间+颜色差异 保边去噪,深度图预处理 参数调起来很头疼

嗯,这里要注意:滤波不是越强越好。我在项目中遇到过一位同事,为了去噪把高斯核设到15x15,结果图像直接变成“油画风格”,边缘全没了。后来抓取失败率飙升到30%。所以我的建议是:从3x3开始试,逐步增大,直到噪声刚好消失为止。

图像增强这块,我常用的是直方图均衡化。特别是当光照不均匀时,比如工件一半亮一半暗,直接做阈值分割会死得很惨。直方图均衡化能把对比度拉开,让暗部细节露出来。代码很简单:

import cv2
img = cv2.imread('part.jpg', 0)
equalized = cv2.equalizeHist(img)

但注意,不要对彩色图直接做全局均衡化,否则颜色会失真。我一般先转成HSV,只对V通道做均衡,再合并回去。

4.2 边缘检测

边缘检测,是视觉引导抓取的核心中的核心。你想想看,机器人要抓一个工件,首先得知道它的轮廓在哪。Canny算子是我最常用的,没有之一。

Canny的流程其实不复杂:

  1. 高斯滤波去噪
  2. 计算梯度幅值和方向
  3. 非极大值抑制(把不是边缘的像素干掉)
  4. 双阈值检测(高阈值确定强边缘,低阈值连接弱边缘)

这里有个关键点:双阈值的设定。我刚开始做的时候,总是用固定阈值,结果换一个工件就崩了。后来我学乖了,用自适应阈值——比如取梯度幅值的均值作为低阈值,高阈值设为低阈值的2-3倍。这样鲁棒性好很多。

举个例子,抓取手机中框时,边缘非常细,如果阈值设高了,边缘会断掉;设低了,又会出现一堆假边缘。我曾经花了一整个下午调参数,最后发现用Otsu自动阈值法效果最好。

import cv2
edges = cv2.Canny(img, threshold1=50, threshold2=150)
# 实际项目中,我会用 Otsu 自动计算阈值
ret, thresh = cv2.threshold(grad, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
edges = cv2.Canny(img, thresh * 0.5, thresh)
我的小技巧: 边缘检测前,先做一次形态学操作(比如开运算),把图像里的小噪点去掉。这样Canny出来的边缘会干净很多。

4.3 轮廓提取

有了边缘,下一步就是提取轮廓。OpenCV里用findContours函数,但这里有个坑——版本差异。OpenCV 3和4的返回值不一样,我当年升级版本时,代码直接报错,排查了半天才发现是返回值顺序变了。

现在的标准写法是:

contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

提取轮廓后,我一般会做几件事:

  • 过滤小轮廓:面积小于某个阈值的直接扔掉,那些都是噪声
  • 多边形逼近:用approxPolyDP把轮廓简化,方便后续匹配
  • 计算矩:用moments求质心,这个就是抓取点

我记得有一次做圆形工件的抓取,轮廓提取出来总是有毛刺,导致质心偏移。后来我用cv2.minEnclosingCircle拟合最小外接圆,效果立竿见影。说白了,不要直接用原始轮廓的质心,用拟合后的几何中心更稳

注意: 轮廓提取前,一定要确保边缘是闭合的。如果边缘有断点,可以用形态学闭运算(先膨胀再腐蚀)把断点连起来。我曾经因为没做这一步,导致轮廓提取出来是散的,抓取点算出来偏了5个像素。

4.4 点云预处理

好了,聊完2D图像,咱们进入3D点云。点云预处理,说白了就是给深度相机拍到的点云“洗个澡”。原始点云数据量巨大,而且充满了离群点和噪声,直接拿去用,算法会崩溃的。

我常用的预处理流程:

  1. 体素降采样:把点云空间划分成小立方体(体素),每个体素内只保留一个点。这样能把几十万个点压缩到几万个,速度提升10倍以上。
  2. 统计滤波:计算每个点邻域内的平均距离,如果距离远大于平均值,就认为是离群点,直接删除。
  3. 半径滤波:如果一个点周围指定半径内没有足够多的邻居,也删掉。

这里我分享一个经验:降采样的体素大小怎么选? 如果工件尺寸是10cm,体素设1mm就太细了,设1cm又太粗。我一般取工件最小特征尺寸的1/5到1/10。比如抓取一个直径5cm的圆盘,体素设5mm左右。

代码示例(PCL库):

pcl::VoxelGrid<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setLeafSize(0.005f, 0.005f, 0.005f);  // 5mm体素
sor.filter(*cloud_filtered);

pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor2;
sor2.setInputCloud(cloud_filtered);
sor2.setMeanK(50);
sor2.setStddevMulThresh(1.0);
sor2.filter(*cloud_clean);
核心要点: 点云预处理的顺序很重要。先降采样,再滤波。如果先滤波再降采样,离群点会消耗大量计算资源,得不偿失。

嗯,说到点云,我还想提一个容易被忽略的点——深度图转点云时的坐标系。很多初学者直接拿深度图转,结果点云是反的或者歪的。我建议转之前先标定相机内参,确保每个像素的深度值对应正确的3D坐标。这个坑我踩过两次,一次是抓取时机器人往反方向走,另一次是点云和实际工件对不上。

最后,我画了一张图,把这一章的知识体系串起来:

图像处理基础:知识体系 原始图像/点云 2D图像处理 滤波与增强 边缘检测 (Canny) 轮廓提取 3D点云处理 体素降采样 统计/半径滤波 干净点云 先2D后3D,先降采样后滤波,最终输出可用于抓取的特征

这张图很直观:原始数据进来后,分两条路走。2D这边做滤波增强、边缘检测、轮廓提取;3D这边做降采样和滤波。两条路最终汇合到抓取特征提取。我个人习惯是先做2D处理,因为2D速度快,能快速定位工件的大致位置,然后再用3D点云做精细的位姿估计。

好了,这一章的内容就这些。记住,预处理做得好,后面识别抓取就成功了一半。别嫌这些基础操作麻烦,我见过太多项目因为预处理没做好,导致整个系统崩溃的例子。稳扎稳打,才是工程师该有的态度。


专注资料整理