二值化,也就是处理结果为0或1,当然是针对图像的各像素而言的
1或0,对应于有无,也就是留下有用的,删除无用的,有用的部分,就是关心的部分
在图像处理中,也不仅仅只是1或0,因为这两个值看起来都是黑的,人眼很难分辨清楚,那就放大一些,255或0,黑白就出来了
目标识别、图像分割、目标提取等后续应用,很多会基于二值化的结果。所以图像分析的二值化处理是一个重要环节。比如CSDN的OpenCV技能树:
广义来说,分析或处理的结果中,各像素点只在0/255间取值,那就算是二值化,所以阈值、腐蚀与膨胀、开运算与闭运算、连通区域分析、轮廓等都算是
但在OpenCV 4中,阈值的结果可能也是彩色图像,开闭运算的输入就是二值化图像… 这个概念可能是错的,因为我还没有从头处理开闭运算。
所以,还是自己按自己的标准来处理,利于理解就行,无所谓对错
OpenCV中的inRange()函数可以实现图像的二值化,其功能是将在两个阈值内的像素值设置为白色(255),而不在阈值区间内的像素值设置为黑色(0)。函数的语法格式如下:
void cv::inRange(InputArray _src, InputArray _lowerb, InputArray _upperb, OutputArray _dst);
其中,_src表示输入图像,可以是灰度图像或彩色图像;_lowerb表示下界的阈值,可以是一个标量值或与输入图像通道数相同的数组;_upperb表示上界的阈值,与_lowerb的类型相同,指定上界阈值;_dst表示输出图像,用于存储计算得到的阈值图像。
二值图像,可作为copyTo的参数,实现原图的部分拷贝
具体实现代码:
cv::Mat originMat = dstMat.clone();
int paramIndex = 0;
bool combineMaskFlag = GetParamValue_Bool(paramIndex++); // 0 - 过滤原图显示
int min0 = GetParamValue_Int(paramIndex++); // 1 - 通道0下限
int max0 = GetParamValue_Int(paramIndex++); // 2 - 通道0上限
int min1 = GetParamValue_Int(paramIndex++); // 3 - 通道1下限
int max1 = GetParamValue_Int(paramIndex++); // 4 - 通道1上限
int min2 = GetParamValue_Int(paramIndex++); // 5 - 通道2下限
int max2 = GetParamValue_Int(paramIndex++); // 6 - 通道2上限
int dim = dstMat.channels();
cv::Mat maskMat;
if(dim == 1)
inRange(dstMat, Scalar(min0), Scalar(max0), dstMat);
if(dim == 2)
inRange(dstMat, Scalar(min0, min1), Scalar(max0, max1), dstMat);
if(dim >= 3)
inRange(dstMat, Scalar(min0, min1, min2), Scalar(max0, max1, max2), dstMat);
if(combineMaskFlag)
originMat.copyTo(dstMat, dstMat);
二值化本质上是针对各像素点进行逻辑判断处理。想明白这点,那就可以实现色通,即在指定彩色颜色相邻区域内OK,其余颜色不再关心。比如绿幕抠图,那就把绿色颜色干掉,只留下其余部分,也就是前景。好象这里说反了,不过理解起来是一样的。
基准偏差,那就是有基准,有偏差,在这个范围内是期望的,出了这个范围就不OK
基准可以是灰度、单通道或彩色,代码写起来很简单,我整成功能函数,以便后续调用
cv::Mat originMat = dstMat.clone();
int paramIndex = 0;
bool combineMaskFlag = GetParamValue_Bool(paramIndex++); // 0 - 过滤原图显示
bool reverseFlag = GetParamValue_Bool(paramIndex++); // 1 - 反相
int channelType = GetParamValue_Int(paramIndex++); // 2 - 基准类型
QColor baseColor = GetParamValue_Color(paramIndex++); // 3 - 基准值
int delta = GetParamValue_Int(paramIndex++); // 4 - 偏差量
if(channelType != 5) { // 灰度基本偏差
if(channelType == 4) // 灰度图
dstMat = CvHelper::ToMat_GRAY(dstMat);
else { // 目标单通道
// 首先要确保有相应的通道存在
int dstChannelNumber = channelType + 1;
if(dstChannelNumber == 2)
dstChannelNumber = 3;
if(dstMat.channels() < dstChannelNumber)
dstMat = CvHelper::ChangeMatDim(dstMat, dstChannelNumber);
std::vector<cv::Mat> channels;
split(dstMat, channels);
dstMat = channels[channelType];
}
dstMat = CvHelper::BuildTransMaskMat(dstMat, baseColor.red() % 0x100, delta);
} else { // 彩色基本偏差
dstMat = CvHelper::BuildTransMaskMat(dstMat, baseColor, delta);
}
if(reverseFlag)
bitwise_not(dstMat, dstMat);
if(combineMaskFlag)
originMat.copyTo(dstMat, dstMat);
// 生成MASK屏蔽图形 - 彩色基准偏差 - 色通 -> transColor ± delta之间通过,之外不过
Mat CvHelper::BuildTransMaskMat(cv::Mat &srcMat, QColor transColor, BYTE delta) {
cv::Mat bgrMat = ToMat_BGR(srcMat);
BYTE r = transColor.red();
BYTE g = transColor.green();
BYTE b = transColor.blue();
BYTE maxR = std::min(255, int(r + delta));
BYTE minR = std::max(0, int(r - delta));
BYTE maxG = std::min(255, int(g + delta));
BYTE minG = std::max(0, int(g - delta));
BYTE maxB = std::min(255, int(b + delta));
BYTE minB = std::max(0, int(b - delta));
cv::Mat maskMat;
inRange(bgrMat, Scalar(minB, minG, minR), Scalar(maxB, maxG, maxR), maskMat);
return maskMat;
}
// 生成MASK屏蔽图形 - 灰度基准偏差 - 灰通 -> transByte ± delta之间通过,之外不过
Mat CvHelper::BuildTransMaskMat(cv::Mat &srcMat, BYTE transByte, BYTE delta) {
cv::Mat grayMat = ToMat_GRAY(srcMat);
cv::Mat resultMat(grayMat.rows, grayMat.cols, CV_8UC1);
BYTE * pSrc = grayMat.data;
BYTE * pDst = resultMat.data;
int low = transByte - delta, high = transByte + delta;
BYTE LOW = std::max(0, low);
BYTE HIGH = std::min(0xFF, high);
for(int row = 0; row < grayMat.rows; ++row)
for(int col = 0; col < grayMat.cols; ++col) {
BYTE v = *pSrc++;
BYTE value = 0x0;
if(IS_IN_RANGE(v, LOW, HIGH))
value = 0xFF;
*pDst++ = value;
}
return resultMat;
}
与基准偏差过滤对应的是分量偏差过滤,即各像素点分量差在目标范围内/外作为选择判断的基准。代码就太简单了。运行效果如下图所示。
为更实用,在逻辑处理前,先将图像转化为RGB三通道,不含A通道即可。