• 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)

    三、图像配准

    如图为模板图像

    如图为待检测图像,我们需要将待检测图像与模板图像进行图像配准。在这里我使用的是基于图像仿射变换进行两幅图像的矫正。关于图像矫正这块就不细说了,可以参考一下我的这篇博文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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    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]);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

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

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

    五、缺陷检测

    以上,我们计算出来了模板的亮、暗阈值图像,主要就是通过与这两幅图像的灰度值进行对比,进而确定缺陷部分。
    在这里插入图片描述
    如图为:将配准对其后的待测图像c(x,y)与差异模型的阈值图像 Tu, l(x,y)进行像素点之间的灰度值对比,当满足如下条件时,即为检测到的缺陷区域。
    c(x,y)>Tu(x,y)∨c(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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    六、效果演示

    1

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

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


    总结

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

  • 相关阅读:
    装配式建筑发展迎来发展新政,加快推进建筑业转型升级
    如何让企业督办管理系统对接第三方应用
    9月17日 杭州站 | Serverless Developer Meetup 开启报名
    Linux C网络通信过程
    Ubuntu Server 20.04 LTS 环境下搭建vim 编辑器Python IDE
    中华人民共和国户口登记条例
    原生 JS 实现 VS Code 自动切换输入法状态!这次没有AHK
    echarts实现折线图点击添加标记
    docker-compose部署mysql
    洛谷 P3275 [SCOI2011]糖果(差分约束,tarjan强连通分量,scc)
  • 原文地址:https://blog.csdn.net/Zero___Chen/article/details/132634606