最近在工作中,要实现自动绘制ROI的功能,但是在代码实现的过程中,遇到了不小的问题,现已解决。
图像的二值化,就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。
二值化是图像分割的一种最简单的方法。二值化可以把灰度图像转换成二值图像。把大于某个临界灰度值的像素灰度设为灰度极大值,把小于这个值的像素灰度设为灰度极小值,从而实现二值化。
根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值。
在处理从一个视频流中获取的图片时,往往因为光线的变化,而导致固定的阈值不适用,这样就会导致图片处理的结果大打折扣,现做如下比较:
原图_1:
在图中我们可以看到,只有这个水杯我们希望是黑色的,背景应该处理成白色。那么进行如下不同阈值的尝试。
阈值_100:
cvtColor(srcClone, srcClone, CV_BGR2GRAY);
threshold(srcClone, srcClone, 100, 255, CV_THRESH_BINARY);
cv::imwrite("D:/阈值_100.jpg", srcClone);
cvtColor(srcClone, srcClone, CV_BGR2GRAY);
threshold(srcClone, srcClone, 110, 255, CV_THRESH_BINARY);
cv::imwrite("D:/阈值_110.jpg", srcClone);
阈值_120:
cvtColor(srcClone, srcClone, CV_BGR2GRAY);
threshold(srcClone, srcClone, 120, 255, CV_THRESH_BINARY);
cv::imwrite("D:/阈值_120.jpg", srcClone);
还是同样的环境(拍摄角度改变了,但还是这个水杯),改变亮度,再来看这个水杯:
原图_2:
阈值_50:
cvtColor(srcClone, srcClone, CV_BGR2GRAY);
threshold(srcClone, srcClone, 50, 255, CV_THRESH_BINARY);
cv::imwrite("D:/阈值_50.jpg", srcClone);
阈值_100:
cvtColor(srcClone, srcClone, CV_BGR2GRAY);
threshold(srcClone, srcClone, 100, 255, CV_THRESH_BINARY);
cv::imwrite("D:/阈值_100.jpg", srcClone);
很明显可以看到,对于亮度改变的原图_2,原先的阈值_100已经不能完全适用,如果将阈值提升到110甚至再往上就会出现大面积黑色的结果,这就是要使用自适应二值化的原因,可以明显降低亮度变化带来的影响(当然不仅仅用于处理亮度变化)。
接下来看看自适应二值化的结果:
原图_1的自适应二值化:
原图_2的自适应二值化:
从以上结果我们可以看出,对于不同亮度的图片,使用自适应二值化后的结果是相差不大的,但是将两种不同的二值化结果相比较可得出,当背景没有过多的影子以及整张图的亮度没有明显变化(比如从左到右变暗)时,自适应二值化后的结果不一定比得过参数调好的固定阈值二值化,这是因为背景光源信息过于简单(大智若愚),但在处理视频流信息的时候,背景光源总会发生不可控的变化,我们肯定不能每次都通过人工来调整阈值,这是不符合自动化工艺的,因此就要使用一个比较好的自适应二值化。
再给出一个例子:
自适应二值化结果:
在这里就不浪费篇幅与固定阈值二值化做比较,读者可自行比较(本人已经试过,效果没那么好)。
void thresholdIntegral(cv::Mat &inputMat, cv::Mat &outputMat)
{
// accept only char type matrices
CV_Assert(!inputMat.empty());
CV_Assert(inputMat.depth() == CV_8U);
CV_Assert(inputMat.channels() == 1);
CV_Assert(!outputMat.empty());
CV_Assert(outputMat.depth() == CV_8U);
CV_Assert(outputMat.channels() == 1);
// rows -> height -> y
int nRows = inputMat.rows;
// cols -> width -> x
int nCols = inputMat.cols;
// create the integral image
cv::Mat sumMat;
cv::integral(inputMat, sumMat);
CV_Assert(sumMat.depth() == CV_32S);
CV_Assert(sizeof(int) == 4);
int S = MAX(nRows, nCols)/8;
double T = 0.15;
// perform thresholding
int s2 = S/2;
int x1, y1, x2, y2, count, sum;
// CV_Assert(sizeof(int) == 4);
int *p_y1, *p_y2;
uchar *p_inputMat, *p_outputMat;
for( int i = 0; i < nRows; ++i)
{
y1 = i-s2;
y2 = i+s2;
if (y1 < 0){
y1 = 0;
}
if (y2 >= nRows) {
y2 = nRows-1;
}
p_y1 = sumMat.ptr<int>(y1);
p_y2 = sumMat.ptr<int>(y2);
p_inputMat = inputMat.ptr<uchar>(i);
p_outputMat = outputMat.ptr<uchar>(i);
for ( int j = 0; j < nCols; ++j)
{
// set the SxS region
x1 = j-s2;
x2 = j+s2;
if (x1 < 0) {
x1 = 0;
}
if (x2 >= nCols) {
x2 = nCols-1;
}
count = (x2-x1)*(y2-y1);
// I(x,y)=s(x2,y2)-s(x1,y2)-s(x2,y1)+s(x1,x1)
sum = p_y2[x2] - p_y1[x2] - p_y2[x1] + p_y1[x1];
if ((int)(p_inputMat[j] * count) < (int)(sum*(1.0-T)))
p_outputMat[j] = 255;
else
p_outputMat[j] = 0;
}
}
}
---
参考自:[添加链接描述](https://www.cnblogs.com/polly333/p/7269153.html)
这套算法以W*W为模板的矩形区域的二维平滑值来代替一维加权值,从而抛开了一维平滑的方向性问题,是Wellner 自适应滤波阈值的升级版。
若有人对算法原理感兴趣或对源码中有不理解的部分可移步下一篇博客,其中会根据自己的理解进行详细介绍。
本文如有问题请积极指出,万分感谢。