一、检测需求
对PCB进行缺陷检测,具体缺陷类型有开路(断路)、短路、缺口、毛刺。
二、问题分析
上图为灰度图,黑色部分为电路板路线,其存在缺口、断路、毛刺、短路等缺陷。这些缺陷有的属于白色缺陷,有的属于黑色缺陷,但都属于小面积缺陷。故,可以使用opencv中的形态学算法,如:腐蚀、膨胀、开运算、闭运算等方法提取这些小面积缺陷。
解决问题的核心思想如下:
用开运算检测毛刺和短路(开运算会消除小面积的白色区域),用闭运算检测缺口和断路(闭运算会消除小面积的黑色区域),开运算与闭运算所的消除结果之和为全部缺陷。
三、基本实现步骤
1、读取图像为灰度图 【imread(“filename”,0),0:灰度图模式】
2、进行开运算与闭运算 【通过形态学操作使缺陷位置发生变化】
3、通过开运算与闭运算结果获取各类缺陷 【将原图与变化结果对比得到缺陷区域灰度图】
4、将缺陷结果进行二值化 【二值化阈值要适度调整】
5、查找二值化结果轮廓画到原图上 【绘图时画笔类型要与图像类型一致】
四、实现过程
4.1 开运算缺陷检测
进行开运算,将原图与开运算结果作差,得到白色缺陷【缺口和断路】。
注释:进行开运算后会移除原图中的白色部分,原图中白色区域比开运算结果多,故用原图去减开运算结果
Mat img=imread("C:\\Users\\aaa\\Desktop\\pcb缺陷检测.png",0);
//进行开运算,将原图与开运算结果作差,得到白色缺陷【缺口和断路】
Mat open_mat, open_mat_defect;
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,5));
morphologyEx(img, open_mat, MORPH_OPEN, element);
open_mat_defect = img - open_mat;
imshow("开运算", open_mat);
imshow("原图-开运算", open_mat_defect);
4.2 闭运算缺陷检测
进行闭运算,将闭运算结果与原图作差,得到黑色缺陷【毛刺和短路】
注释:进行闭运算后会移除原图中的黑色部分,闭运算结果图中白色区域比原图多,故用闭运算结果去减原图
//进行闭运算,将闭运算结果与原图作差,得到黑色缺陷【毛刺和短路】
Mat close_mat, close_mat_defect;
cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size( 5, 5));
morphologyEx(img, close_mat, MORPH_CLOSE, element2);
close_mat_defect = close_mat- img;
imshow("闭运算", close_mat);
imshow("闭运算-原图", close_mat_defect);
4.3 缺陷二值化
闭运算缺陷和开运算缺陷叠加,然后二值化。二值化的阈值要根据效果调整
//闭运算缺陷和开运算缺陷叠加,然后二值化
Mat defect_2zh;
Mat defect = open_mat_defect + close_mat_defect;
threshold(defect, defect_2zh, 58, 255, THRESH_BINARY);
imshow("所有缺陷", defect);
imshow("缺陷二值化", defect_2zh);
4.4 缺陷绘图
将检测出的缺陷绘制在原图上,这里需要注意的是原图被读取为灰度图,想绘制彩色,故需要将其从灰度图转换为彩色图BGR模式。
//将检测出的缺陷绘制在原图上
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(defect_2zh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
cvtColor(img, img, cv::COLOR_GRAY2BGR);//原图被读取为灰度图,想绘制彩色,故进行转换
//遍历所有轮廓,绘制缺陷轮廓
for (int i = 0; i < contours.size(); i++)
{
//绘制轮廓
drawContours(img, contours, i, Scalar(0, 0, 255), -1, 8, hierarchy);
}
imshow("原图缺陷", img);
4.5 完整代码
/*
对PCB进行缺陷检测,具体缺陷类型有开路(断路)、短路、缺口、毛刺。
核心思想:用开运算检测毛刺,用闭运算检测缺口,开运算与闭运算结果之和为全部缺陷
基本步骤:
1、读取图像为灰度图 【imread("filename",0),0:灰度图模式】
2、进行开运算与闭运算 【通过形态学操作使缺陷位置发生变化】
3、通过开运算与闭运算结果获取各类缺陷 【将原图与变化结果对比得到缺陷区域灰度图】
4、将缺陷结果进行二值化 【二值化阈值要适度调整】
5、查找二值化结果轮廓画到原图上 【绘图时画笔类型要与图像类型一致】
*/
Mat img=imread("C:\\Users\\aaa\\Desktop\\pcb缺陷检测.png",0);
//进行开运算,将原图与开运算结果作差,得到白色缺陷【缺口和断路】
Mat open_mat, open_mat_defect;
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,5));
morphologyEx(img, open_mat, MORPH_OPEN, element);
open_mat_defect = img - open_mat;
imshow("开运算", open_mat);
imshow("原图-开运算", open_mat_defect);
//进行闭运算,将闭运算结果与原图作差,得到黑色缺陷【毛刺和短路】
Mat close_mat, close_mat_defect;
cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size( 5, 5));
morphologyEx(img, close_mat, MORPH_CLOSE, element2);
close_mat_defect = close_mat- img;
imshow("闭运算", close_mat);
imshow("闭运算-原图", close_mat_defect);
//闭运算缺陷和开运算缺陷叠加,然后二值化
Mat defect_2zh;
Mat defect = open_mat_defect + close_mat_defect;
threshold(defect, defect_2zh, 58, 255, THRESH_BINARY);
imshow("所有缺陷", defect);
imshow("缺陷二值化", defect_2zh);
//将检测出的缺陷绘制在原图上
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(defect_2zh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
cvtColor(img, img, cv::COLOR_GRAY2BGR);//原图被读取为灰度图,想绘制彩色,故进行转换
//遍历所有轮廓,绘制缺陷轮廓
for (int i = 0; i < contours.size(); i++)
{
//绘制轮廓
drawContours(img, contours, i, Scalar(0, 0, 255), -1, 8, hierarchy);
}
imshow("原图缺陷", img);
waitKey( );
4.6 效果分析
通过对上述结果图分析,发现该算法存在一定程度的误检,具体如下图所示,共存在17处误检。这些误检区域与缺口(黑色的才是线路)存在相同的形态学特征(有个白色的尖尖),使用开运算很难避免该类误检。目前,开运算所能准确检测到的缺口类型缺陷均为线路上缺陷,对于非线路上缺陷均为误检。
五、算法优化
目前算法的误检由开运算导致,均发生在非线路区域,故需要移除由开运算在非线路区域的检测结果。
5.1 优化思路
1.提取非线路区域
2.提取开运算结果
3.移除开运算结果在非线路区域的结果
5.2 优化过程
在优化思路中,提取提取非线路区域是关键操作。非线路区域的特点是,其为图像中黑色较粗的区域;线路区域的特点是,其为图像中黑心较细的区域;这些线条区域与非线条区域基本上都在一个在连通域中。故,不可以使用面积提取,需要使用闭运算来提取图像中黑色较粗的区域。
同时,需要注意闭运算提取的黑色较粗区域可能无法盖住闭运算所检测出的缺陷,故需要对黑色较粗区域进行腐蚀(使白色区域能盖住闭运算所检测出的缺陷)。
核心代码如下所示:
//----新增:为实现性能优化
//获取图像中的非线路区域(其特点是比较粗)
//使用闭运算移除非线路区域
Mat img_bin, img_bin_close, img_bin_close_dilate;
threshold(img, img_bin, 0, 255, THRESH_OTSU);
cv::Mat element_close = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 15));
morphologyEx(img_bin, img_bin_close, MORPH_CLOSE, element_close);
img_bin_close = 255 - img_bin_close;//使非线条区域从黑变白
imshow("img_bin_close", img_bin_close);
cv::Mat element_dilate = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(11, 11));
morphologyEx(img_bin_close, img_bin_close_dilate, MORPH_DILATE, element_dilate);
imshow("img_bin_close", img_bin_close);
5.2 优化效果及代码
优化后的效果及代码如下所示,可以看到通过上述步骤后消除了大部分的误检。
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <vector>
#include <io.h>
#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
int main() {
Mat img=imread("C:\\Users\\hpg\\Desktop\\pcb缺陷检测.png",0);
//----新增:为实现性能优化
//获取图像中的非线路区域(其特点是比较粗)
//使用闭运算移除非线路区域
Mat img_bin, img_bin_close, img_bin_close_dilate;
threshold(img, img_bin, 0, 255, THRESH_OTSU);
cv::Mat element_close = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 15));
morphologyEx(img_bin, img_bin_close, MORPH_CLOSE, element_close);
img_bin_close = 255 - img_bin_close;//使非线条区域从黑变白
imshow("img_bin_close", img_bin_close);
cv::Mat element_dilate = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(11, 11));
morphologyEx(img_bin_close, img_bin_close_dilate, MORPH_DILATE, element_dilate);
imshow("img_bin_close", img_bin_close);
//进行开运算,将原图与开运算结果作差,得到白色缺陷【缺口和断路】
Mat open_mat, open_mat_defect;
cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5,5));
morphologyEx(img, open_mat, MORPH_OPEN, element);
open_mat_defect = img - open_mat;
imshow("开运算", open_mat);
imshow("原图-开运算", open_mat_defect);
//移除开运算在非线条区域的误检
open_mat_defect = open_mat_defect - img_bin_close;
//进行闭运算,将闭运算结果与原图作差,得到黑色缺陷【毛刺和短路】
Mat close_mat, close_mat_defect;
cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size( 5, 5));
morphologyEx(img, close_mat, MORPH_CLOSE, element2);
close_mat_defect = close_mat- img;
imshow("闭运算", close_mat);
imshow("闭运算-原图", close_mat_defect);
//闭运算缺陷和开运算缺陷叠加,然后二值化
Mat defect_2zh;
Mat defect = open_mat_defect + close_mat_defect;
threshold(defect, defect_2zh, 58, 255, THRESH_BINARY);
imshow("所有缺陷", defect);
imshow("缺陷二值化", defect_2zh);
//将检测出的缺陷绘制在原图上
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(defect_2zh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
cvtColor(img, img, cv::COLOR_GRAY2BGR);//原图被读取为灰度图,想绘制彩色,故进行转换
//遍历所有轮廓,绘制缺陷轮廓
for (int i = 0; i < contours.size(); i++)
{
//绘制轮廓
drawContours(img, contours, i, Scalar(0, 0, 255), -1, 8, hierarchy);
}
imshow("原图缺陷", img);
waitKey( );
}