OpenCV C++案例实战三十三《缺陷检测》

OpenCV C++案例实战三十三《缺陷检测》


前言

本案例将使用OpenCV C++ 进行PCB印刷缺陷检测。目前缺陷检测算法可分为两大类:
一:基于模板匹配的缺陷检测
二:基于深度学习的缺陷检测,主要利用目标检测去识别缺陷部分。
本文算法主要是基于模板匹配算法进行缺陷检测,参考《基于差异模型的印刷标签缺陷检测算法》一文,进行算法复现,感兴趣的朋友可以去阅读一下原文。
在这里插入图片描述

一、结果演示

在这里插入图片描述

二、缺陷检测算法

2.1、多元模板图像

通过工业相机采集合格标签图像,作为差异模型的训练数 据集,选择其中一张合格标签图像分别进行高斯平滑、灰度腐蚀 和灰度膨胀操作,获取多元模板图像,用于训练差异模型。
将合格图像f(x,y)与高斯核滤波器卷积,得到高斯平滑图像f1(x,y)。 构建一个11×11大小的矩形结构元素,对合格标签图像进 行灰度腐蚀运算,得到灰度腐蚀图像f2(x,y)。再构建一个13×13 大小的矩形结构元素,对合格标签图像进行灰度膨胀运算[3],得到灰度膨胀图像f3(x,y)。

2.2、训练差异模型

将多元模板图像f1(x,y)、f 2(x,y)与f 3(x,y)作为训练数据集 对差异模型进行训练。对所有图像同一坐标的像素点计算平均 值与标准差[4],得到均值图像F(x,y):
在这里插入图片描述

标准差图像V(x,y):
在这里插入图片描述

本文中,F(x,y)、V(x,y)即为差异模型训练过程中的标准图 像与差异图像。

为了使理想的差异模型适应正常的工艺误差范围,加入相对阈值VarThreshold=[b u,b l]参数。 其中,b u为上限相对阈 值,bl为下限相对阈值。如图2所示。则两幅阈值图像T u,l(x,y) 计算如下:
亮阈值图像:Tu(x,y)=F(x,y)+ bu* V(x,y)
暗阈值图像:Tl(x,y)=F(x,y)- bl* V(x,y)

将配准对其后的待测图像c(x,y)与差异模型的阈值图像 Tu, l(x,y)进行像素点之间的灰度值对比,当满足如下条件时,即为检测到的缺陷区域。
c(x,y)>Tu(x,y)∨c(x,y)<T l (x,y)

三、图像配准

如图为模板图像

如图为待检测图像,我们需要将待检测图像与模板图像进行图像配准。在这里我使用的是基于图像仿射变换进行两幅图像的矫正。关于图像矫正这块就不细说了,可以参考一下我的这篇博文OpenCV C++案例实战四《图像透视矫正》。这里直接上代码
在这里插入图片描述

3.1 功能源码

//图像定位矫正
bool ImageLocal(cv::Mat srcImg, cv::Mat& warpImg, Point2f SrcAffinePts[])
{
	Mat grayImg;
	if (srcImg.channels() != 1)
	{
		cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
	}
	else
	{
		grayImg = srcImg.clone();
	}

Mat blurImg;
medianBlur(grayImg, blurImg, 5);

Mat binImg;
threshold(blurImg, binImg, 10, 255, THRESH_BINARY);
//namedWindow(“binImg”, WINDOW_NORMAL);
//imshow(“binImg”, binImg);

vector<vector<Point>>contours;
findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
RotatedRect bRect;
for (int cnt = 0; cnt < contours.size(); cnt++)
{
double area = contourArea(contours[cnt]);
if (area > 1000)
{
bRect = minAreaRect(contours[cnt]);
}
}

if (bRect.size.empty())return false;//如果没有找到最小外接矩形,返回false

//找到最小外接矩形四个顶点
Point2f srcPoints[4];
bRect.points(srcPoints);
//for (int i = 0; i < 4; i++)
//{
// line(srcImg, srcPoints[i], srcPoints[(i + 1) % 4], Scalar(0, 255, 0), 3);
//}

//将四个点按照左上、右上、右下、左下进行区分
int TL, TR, BR, BL;
double addmax = 0.0, addmin = 999.9, submax = 0.0, submin = 999.9;
for (int i = 0; i < 4; i++)
{
double addval = srcPoints[i].x + srcPoints[i].y;
double subval = srcPoints[i].x - srcPoints[i].y;
if (addval > addmax)
{
addmax = addval;
BR = i;
}
if (addval < addmin)
{
addmin = addval;
TL = i;
}
if (subval > submax)
{
submax = subval;
TR = i;
}
if (subval < submin)
{
submin = subval;
BL = i;
}
}

double LeftHeight = EuDis(srcPoints[TL], srcPoints[BL]);
double RightHeight = EuDis(srcPoints[TR], srcPoints[BR]);
double MaxHeight = max(LeftHeight, RightHeight);

double UpWidth = EuDis(srcPoints[TL], srcPoints[TR]);
double DownWidth = EuDis(srcPoints[BL], srcPoints[BR]);
double MaxWidth = max(UpWidth, DownWidth);

//这里使用的顺序是左上、右上、右下、左下顺时针顺序。SrcAffinePts、DstAffinePts要一一对应
SrcAffinePts[0] = Point2f(srcPoints[TL]);
SrcAffinePts[1] = Point2f(srcPoints[TR]);
SrcAffinePts[2] = Point2f(srcPoints[BR]);
SrcAffinePts[3] = Point2f(srcPoints[BL]);
Point2f DstAffinePts[4] = { Point2f(0,0),Point2f(MaxWidth,0),Point2f(MaxWidth,MaxHeight),Point2f(0,MaxHeight) };

Mat M = getPerspectiveTransform(SrcAffinePts, DstAffinePts);

warpPerspective(srcImg, warpImg, M, Size(MaxWidth, MaxHeight), 1, 0, Scalar::all(0));

return true;
}

    3.1 功能效果

    在这里插入图片描述

    四、多元模板图像

    关于如何计算均值图像、差异图像、以及亮、暗阈值图像在下面源码中以复现,具体请阅读源码。

    4.1 功能源码

    //计算均值图像
    void meanImage(cv::Mat gaussianImg, cv::Mat erodeImg, cv::Mat dilateImg, cv::Mat& meanImg)
    {
    	meanImg = Mat::zeros(gaussianImg.size(), CV_8U);
    	for (int i = 0; i < gaussianImg.rows; i++)
    	{
    		uchar* gData = gaussianImg.ptr<uchar>(i);
    		uchar* eData = erodeImg.ptr<uchar>(i);
    		uchar* dData = dilateImg.ptr<uchar>(i);
    		uchar* mData = meanImg.ptr<uchar>(i);
    

    for (int j = 0; j < gaussianImg.cols; j++)
    {
    mData[j] = (gData[j] + eData[j] + dData[j]) / 3;
    }
    }
    }

    //计算差异图像
    void diffImage(cv::Mat gaussianImg, cv::Mat erodeImg, cv::Mat dilateImg, cv::Mat meanImg, cv::Mat& diffImg)
    {
    diffImg = Mat::zeros(gaussianImg.size(), CV_8U);
    for (int i = 0; i < gaussianImg.rows; i++)
    {
    uchar gData = gaussianImg.ptr<uchar>(i);
    uchar eData = erodeImg.ptr<uchar>(i);
    uchar dData = dilateImg.ptr<uchar>(i);
    uchar mData = meanImg.ptr<uchar>(i);
    uchar* Data = diffImg.ptr<uchar>(i);

    for (int j = 0; j < gaussianImg.cols; j++)
    {
    Data[j] = sqrt(powf((gData[j] - mData[j]), 2) + powf((eData[j] - mData[j]), 2) + powf((dData[j] - mData[j]), 2) / 3.0);
    }
    }
    }

    //计算亮、暗阈值图像
    void threshImg(cv::Mat meanImg, cv::Mat diffImg,cv::Mat &LightImg,cv::Mat& DarkImg)
    {
    double bu = 1.2;
    double bl = 0.8;

    Mat mul_bu, mul_bl;
    multiply(diffImg, bu, mul_bu);
    multiply(diffImg, bl, mul_bl);

    LightImg = Mat::zeros(meanImg.size(), CV_8U);
    DarkImg = Mat::zeros(meanImg.size(), CV_8U);

    for (int i = 0; i < meanImg.rows; i++)
    {
    uchar mData = meanImg.ptr<uchar>(i);
    uchar dData = diffImg.ptr<uchar>(i);
    uchar lData = LightImg.ptr<uchar>(i);
    uchar DData = DarkImg.ptr<uchar>(i);
    uchar buData = mul_bu.ptr<uchar>(i);
    uchar blData = mul_bl.ptr<uchar>(i);

    for (int j = 0; j < meanImg.cols; j++)
    {
    lData[j] = saturate_cast<uchar>(mData[j] + buData[j]);
    DData[j] = saturate_cast<uchar>(mData[j] - blData[j]);
    }
    }
    }

      如下图为亮阈值图像。
      在这里插入图片描述

      如下图为暗阈值图像。
      在这里插入图片描述

      五、缺陷检测

      以上,我们计算出来了模板的亮、暗阈值图像,主要就是通过与这两幅图像的灰度值进行对比,进而确定缺陷部分。
      在这里插入图片描述
      如图为:将配准对其后的待测图像c(x,y)与差异模型的阈值图像 Tu, l(x,y)进行像素点之间的灰度值对比,当满足如下条件时,即为检测到的缺陷区域。
      c(x,y)>Tu(x,y)∨c(x,y)<T l (x,y)

      由于此时提取到的缺陷部分是基于仿射矫正后的,故如果需要在原图上显示结果的话,还需要将检测结果进行反变换回去。具体请阅读源码。

      5.1 功能源码

      //缺陷检测
      void DetectImg(cv::Mat warpImg,cv::Mat LightImg, cv::Mat DarkImg, Point2f SrcAffinePts[],cv::Mat decImg, cv::Mat& showImg)
      {
      	int th = 10;//容差阈值
      

      Mat resImg = Mat::zeros(warpImg.size(), CV_8U);
      for (int i = 0; i < warpImg.rows; i++)
      {
      uchar sData = warpImg.ptr<uchar>(i);
      uchar lData = LightImg.ptr<uchar>(i);
      uchar dData = DarkImg.ptr<uchar>(i);
      uchar rData = resImg.ptr<uchar>(i);

      for (int j = 0; j < warpImg.cols; j++)
      {
      //识别缺陷
      if ((sData[j]-th) > lData[j]||(sData[j]+th) < dData[j])
      {
      rData[j] = 255;
      }
      }
      }

      Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
      morphologyEx(resImg, resImg, MORPH_OPEN, kernel);

      kernel = getStructuringElement(MORPH_RECT, Size(7, 7));
      dilate(resImg, resImg, kernel);

      //namedWindow(“resImg”, WINDOW_NORMAL);
      //imshow(“resImg”, resImg);

      //绘制缺陷结果
      vector<vector<Point>>contours;
      findContours(resImg, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
      for (int t = 0; t < contours.size(); t++)
      {
      if (contourArea(contours[t]) > 50)
      {
      Rect rect = boundingRect(contours[t]);
      rectangle(showImg, rect, Scalar(0, 0, 255), 2);
      }
      }

      //将结果反变换回原图像
      Point2f DstAffinePts[4] = { Point2f(0,0),Point2f(decImg.cols,0),Point2f(decImg.cols,decImg.rows),Point2f(0,decImg.rows) };

      Mat M = getPerspectiveTransform( DstAffinePts, SrcAffinePts);

      warpPerspective(showImg, showImg, M, decImg.size(), 1, 0, Scalar::all(0));
      }

        六、效果演示

        1

        在这里插入图片描述
        在这里插入图片描述
        如上图效果所示,与模板图像对比,基本上将待测图像里的缺陷全部检测,而且误检情况很少。上应用到不同物体检测时,需要根据自己的图像数据进行稍小的调参。在这里只是给大家提供一个算法思路,欢迎大家进行交流学习!!!

        在这里插入图片描述
        已经开通微信公众号啦!欢迎大家关注呀!公众号会不定期更新OpenCV系列文章以及相关源代码资料等!欢迎大家关注交流学习!


        总结

        本文使用OpenCV C++ 进行PCB印刷缺陷检测,主要操作有以下几点。
        1、将图像进行仿射变换,与模板图像进行配准
        2、计算差异图像,得到基于模板的亮、暗阈值图像
        3、将待检测图像与亮、暗阈值图像逐像素比较,设定阈值,超出阈值部分的即为缺陷

        • 0
          点赞
        • 0
          收藏
          觉得还不错? 一键收藏
        • 0
          评论
        评论
        添加红包

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

        当前余额3.43前往充值 >
        需支付:10.00
        成就一亿技术人!
        领取后你会自动成为博主和红包主的粉丝 规则
        hope_wisdom
        发出的红包
        实付
        使用余额支付
        点击重新获取
        扫码支付
        钱包余额 0

        抵扣说明:

        1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
        2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

        余额充值