在前面我们已经完成通过OpenCV读取图片在Picture Control中并自适应显示。下面我们很自然的需要对读取的图片进行处理,处理完之后很自然的想到要看看效果。所以今天的笔记是通过OpenCV灰度处理图像并显示结果。
如图所示,
S1,上面的按钮打开资源管理器,载入一个图片到左边的Picture Control自适应显示;
S2,下面一个按钮把载入的图片通过OpenCV灰度处理后,在右边的Picture Control自适应显示;
S3,通过下面的滚动条来调整阈值,观察并感性认知,不同阈值下灰度化的不同效果。
以我这新手村的理解,如果说数字图像预处理的最后一步是图像的二值化,那么第一步就是图像的灰度化。
图像灰度化处理的目的简单点说就是把图像数字化,方便技术开发和分析。
灰度图像每个像素只需一个字节存放灰度值(又称强度值、亮度值),灰度范围为0-255,灰度图像通常在单个电磁波频谱(如可见光)内测量每个像素的亮度得到的。用于显示的灰度图像通常用每个采样像素8位的非线性尺度来保存,这样可以有256级灰度。这种精度刚刚能够避免可见的条带失真,并且非常易于编程。
图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出明显的黑白效果。在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。
从编程的角度,可以理解为把图像中的复杂像素信息,转换成非0即1,非1就0的二值化数据,便于我们计算。
//第二个按钮的响应事件代码
void CmyMFCDlg::OnBnClickedBinary()
{
// TODO: 在此添加控件通知处理程序代码
Mat dst_mat;//左边载入的图片对象
//S1,灰度处理
cv::cvtColor(src_mat, dst_mat, cv::COLOR_BGR2GRAY);
//S2,开始二值化
//第二个和第三个参数是阀值,决定了二值化显示的效果.如果设定不正确可能图像显不出来
cv::threshold(dst_mat, dst_mat, 127, 255, cv::THRESH_BINARY);
//S3,展示图片处理后的效果
DrawMat(dst_mat, IDC_STATIC_RESULT);
}
关于二值化方法cv::threshold简单说下,一共5个参数,重点注意下第3和第5个参数。
第3个是阈值,第5个是阈值算法类型。
这里的阈值怎么理解?比如我们的相机固定的角度和参数去拍摄一个照片,那么受光源变化(比如阳光变化,比如灯光明暗)的影响,出来的图像也可能会不一样。我们不能简单粗暴的去对图片二值化,这样会导致可能丢失本该是1的像素信息,却收集放大化了本该是0的像素信息。
大多数时候,在工业流水线环境下,由于光源和角度相对固定,所以这阈值是可以通过反复调试后的经验值来设定一个最佳阈值和阈值算法类型。但是在光源环境变化较多的时候,这阈值需要我们更多的精力去计算。
我们这就来看看同一个图片,在不同阈值的图片效果:
看到滑块的变化,导致了阈值的变化,也导致了二值化后的效果在变化,哪些值是有用的,哪些值会计算出什么结果,这都会有阈值不同带来不同的结果。
知道这阈值的影响之强大了吧?
至于cv::threshold第5个参数阈值算法类型,也是和阈值相辅相成的,这些网上都比较全面,也比较容易查阅,就不多说了。
最后贴下文首提到的第3点需求,OpenCV处理过后的图像要在Picture Control展现出来。
//参数1:要显示的图对象
//参数2:Picture Control控件的ID
void CmyMFCDlg::DrawMat(cv::Mat& img, UINT nID)
{
CRect rect;
cv::Mat imgTmp;
GetDlgItem(nID)->GetClientRect(&rect); // 获取控件大小
cv::resize(img, imgTmp, cv::Size(rect.Width(), rect.Height()));// 缩放Mat并备份
// 再重新灰度处理下,备用
switch (imgTmp.channels())
{
case 1:
cv::cvtColor(imgTmp, imgTmp, CV_GRAY2BGRA); // GRAY单通道
break;
case 3:
cv::cvtColor(imgTmp, imgTmp, CV_BGR2BGRA); // BGR三通道
break;
default:
break;
}
int pixelBytes = imgTmp.channels() * (imgTmp.depth() + 1); // 计算一个像素多少个字节
// 制作bitmapinfo(数据头)
BITMAPINFO bitInfo;
bitInfo.bmiHeader.biBitCount = 8 * pixelBytes;
bitInfo.bmiHeader.biWidth = imgTmp.cols;
bitInfo.bmiHeader.biHeight = -imgTmp.rows;
bitInfo.bmiHeader.biPlanes = 1;
bitInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitInfo.bmiHeader.biCompression = BI_RGB;
bitInfo.bmiHeader.biClrImportant = 0;
bitInfo.bmiHeader.biClrUsed = 0;
bitInfo.bmiHeader.biSizeImage = 0;
bitInfo.bmiHeader.biXPelsPerMeter = 0;
bitInfo.bmiHeader.biYPelsPerMeter = 0;
// Mat.data + bitmap数据头 -> MFC
CDC* pDC = GetDlgItem(nID)->GetDC();
::StretchDIBits(
pDC->GetSafeHdc(),
0, 0, rect.Width(), rect.Height(),
0, 0, rect.Width(), rect.Height(),
imgTmp.data,
&bitInfo,
DIB_RGB_COLORS,
SRCCOPY
);
ReleaseDC(pDC);
}
身为MFC和图像处理的双重新手,学习起来比较吃力,但日积月累,总会有收获,加油了!