最近做一个工业盒子自动抓取的项目,涉及一项重要的算法:二值化。
要求盒子的抓取准确率 99.9% 以上,坐标精度在0.01-0.05毫米,所以难度颇大。
稳定的二值化,稳定的轮廓提取,稳定线条提取,稳定的亚像素逼近,以及高精度的相机标定都是这个项目最核心的地方。
关于二值化算法,我了解的就有很多:全局二值化,局部二值化等。
otsu, nat, niblack, nick,sauvola, wolf, adaptive threshold, ……还有最大熵,三角阈值,P分位数 ...
图像处理有一个重要的思想:基于场景最优化,才能真正解决应用问题。
由于纸盒有一定的随机性:旋转,视野忽大忽小,以及光照不均……
用 著名的 二值化真解决不了真正问题,比如 OSTU,达不到 每次 边缘分割很好。
考虑前景、背景特点,设计 基于最大块、最小块 的均值作为阈值,进行全局二值化算法。
边缘明显稳定多了,效果如下:
搞工业图像处理,就是要不断深入理解场景,找寻最合适的优化。
算法源代码如下:
COMMON_LIB_DECL void binary_min_max_block(const Mat& src, Mat& dst,
const Size& minBlock = Size(5, 5), const Size& maxBlock = Size(5, 5))
//搜索最小灰度块/最大灰度块,取均值
{
//积分图,用于加速
Mat intMat;
integral_transform(src, intMat);
//搜索的块尺寸
Rect minRect, maxRect;
double maxV = (std::numeric_limits<int>::min)();
double minV = (std::numeric_limits<int>::max)();
for (int i = 0; i < src.rows; ++i)
{
for (int j = 0; j < src.cols; ++j)
{
if (i + minBlock.height < src.rows && j + minBlock.width < src.cols)//越界判断
{
Rect _rmin = Rect(j, i, minBlock.width, minBlock.height); //求最小块
double _sumMinBlockV = intergal_of_sum(intMat, _rmin);
if (_sumMinBlockV < minV)
{
minRect = _rmin;
minV = _sumMinBlockV;
}
}
if (i + maxBlock.height < src.rows && j + maxBlock.width < src.cols)//越界判断
{
Rect _rmax = Rect(j, i, maxBlock.width, maxBlock.height);//求最大块
double _sumMaxBlockV = intergal_of_sum(intMat, _rmax);
if (_sumMaxBlockV > maxV)
{
maxRect = _rmax;
maxV = _sumMaxBlockV;
}
}
}
}
#if 0 //调试开关
Mat colorImg;
cv::cvtColor(src, colorImg, CV_GRAY2BGR);
cv::rectangle(colorImg, minRect, Scalar(255,0,0));
cv::rectangle(colorImg, maxRect, Scalar(0,0,255));
cv::imshow("colorImg", colorImg);
cv::waitKey();
#endif
double avgMax = maxV/maxBlock.area();
double avgMin = minV/minBlock.area();
double thres = (avgMax + avgMin)/2;
cv::threshold(src, dst, thres, 255, CV_THRESH_BINARY);
}
测试:
有时候看不懂 opencv的函数命名,我就重定义一个封装API,做到一目了然,整理成模块。
intergal_transform 封装了 积分图调用。
intergal_sum 封装了 快速求取 左上角->右下角 的子块面积。
read_gray 就是 封装了代码,避免 每次去查文档,找枚举变量。
get_under_images 就是封装了 boost::filesystem::recursive_directory_iterator 读取所有 opencv支持的图片格式。
resize_to_width 封装了按照 固定宽度的比例尺 resize图片。
binary_min_max_block就是调用我设计的 二值化API.
show_img 代码有点多,主要是 避免了一些矩阵显示问题,imshow显示的特点:
8-bit unsigned直接显示
16-bit unsigned or 32-bit integer 像素点除以 256 ,把 [0,255*256] --> [0,255]
32-bit floating 像素点 x 255, 即把 [0,1] --> [0,255]
小结:
目前这个二值化算法,已经作为这个项目的标准的自动二值化算法, 当然也保留了手动设定阈值的二值化。
搞图像处理,最重要的一点,就是算法可控,完备性。越是高级算法,可控性就越小,想做稳定就越困难。
也许,我们可能会用 预处理:伽马矫正, 直方图均衡,直方图规定化,然后二值化,或者边缘提取: Canny 等等其他算法,但是完备性不足,也许解决了 98%的场景,但是我们的要求是 99.9%.