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

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

  • 前言
  • 一、结果演示
  • 二、缺陷检测算法
    • 2.1、多元模板图像
    • 2.2、训练差异模型
  • 三、图像配准
    • 3.1 功能源码
    • 3.1 功能效果
  • 四、多元模板图像
    • 4.1 功能源码
  • 五、缺陷检测
    • 5.1 功能源码
  • 六、效果演示
  • 总结


前言

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/100372.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区块链实验室(18) - 用FISCO BCOS架设1个无标度网络

FISCO技术文档提供1个4节点的网络案例&#xff0c;这4个节点构成1个强连通图。强连通图在现实中通常是不存在的。 本文用FISCO架设1个网络&#xff0c;该网络由100个节点构成1个无标度(scale free)网络&#xff0c;如下图所示。 1 用FISCO工具构建1个100节点的初始网络 FISCO提…

vue3项目导入异常Error: @vitejs/PLUGIN-vue requires vue (>=3.2.13)

vue3项目导入异常 1、异常提示如下&#xff1a; failed TO LOAD config FROM D:\ws-projects\vite.co nfig.js error WHEN STARTING dev SERVER: Error: vitejs/PLUGIN-vue requires vue (>3.2.13) OR vue/compiler-sfc TO be pre sent IN the dependency tree.2、解决办法…

【SpringSecurity】十、JWT工具类

文章目录 1、jwt类库与相关依赖2、工具类3、总结 1、jwt类库与相关依赖 <!-- 添加jwt的依赖 --> <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.11.0</version> </dependency>…

unity 跨屏显示

1.代码 /*Type:设置分辨率*/ using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.Runtime.InteropServices;public class ScreenManager : MonoBehaviour {[HideInInspector]//导入设置窗口函数 [DllImport("…

执行SQL文件出现【Unknown collation “utf8mb4_0900_ai_ci”】的解决方案

执行SQL文件出现【Unknown collation “utf8mb4_0900_ai_ci”】的解决方案 一、背景描述二、报错原因三、解决方案 一、背景描述 从服务器MySQL中导出数据为SQL执行脚本后&#xff0c;在本地执行导出的SQL脚本。 报错&#xff1a;Unknown collation “utf8mb4_0900_ai_ci” …

浅谈城市轨道交通视频监控与AI视频智能分析解决方案

一、背景分析 地铁作为重要的公共场所交通枢纽&#xff0c;流动性非常高、人员大量聚集&#xff0c;轨道交通需要利用视频监控系统来实现全程、全方位的安全防范&#xff0c;这也是保证地铁行车组织和安全的重要手段。调度员和车站值班员通过系统监管列车运行、客流情况、变电…

【C++从0到王者】第二十五站:多继承的虚表

文章目录 前言一、多继承的虚函数表二、菱形继承与菱形虚拟继承的虚函数表1.菱形继承2.菱形虚拟继承的虚函数表 三、抽象类1.抽象类的概念2.接口继承与实现继承 总结 前言 其实关于单继承的虚函数表我们在上一篇文章中已经说过了&#xff0c;就是派生类中的虚表相当于拷贝了一…

高版本springboot3.1配置Eureka客户端问题

只需要按上面配置好&#xff0c;然后高版本的Eureka&#xff0c;不需要EnableEurekaClient这个注解了&#xff0c;直接SpringBoot启动&#xff0c;就可以注册到注册中心。 /*********************************************************/ /** * 开启eureka客户端功能 */ //E…

说说Flink中的State

分析&回答 基本类型划分 在Flink中&#xff0c;按照基本类型&#xff0c;对State做了以下两类的划分&#xff1a; Keyed State&#xff0c;和Key有关的状态类型&#xff0c;它只能被基于KeyedStream之上的操作&#xff0c;方法所使用。我们可以从逻辑上理解这种状态是一…

如何有效防止服务器被攻击?

随着互联网的快速发展&#xff0c;服务器安全问题日益引起人们的关注。近期&#xff0c;全球范围内频繁发生的服务器攻击事件引发了广泛关注。为了保护企业和个人的数据安全&#xff0c;有效防止服务器被攻击已成为迫在眉睫的任务。 首先&#xff0c;及时更新服务器的操作系统和…

Visual Studio编译出来的程序无法在其它电脑上运行

在其它电脑&#xff08;比如Windows Server 2012&#xff09;上运行Visual Studio编译出来的应用程序&#xff0c;结果报错&#xff1a;“无法启动此程序&#xff0c;因为计算机中丢失VCRUNTIME140.dll。尝试重新安装该程序以解决此问题。” 解决方法&#xff1a; 属性 -> …

Linux线程互斥

目录 一、线程不安全 1.线程不安全现象 2.线程不安全程序的特质 3.线程不安全程序的原因 二、线程互斥 1.基本概念 2.锁 &#xff08;1&#xff09;认识锁 &#xff08;2&#xff09;互斥锁的使用 &#xff08;3&#xff09;代码的改造 3.锁的本质 &#xff08;1&a…

读word模板批量生成制式文件

文章目录 1、Maven依赖2、.docx或.doc格式的word模板准备3、读word模板&#xff0c;批量替换代码域&#xff0c;生成文件&#xff0c;demo4、结果展示 1、Maven依赖 <dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagre…

企业级智能PDF及文档处理SDK GdPicture.NET 14.2 Crack

企业级智能PDF及文档处理SDK GdPicture.NET 提供了一组非常先进的 API&#xff0c;这些 API 利用了人工智能、机器学习和模糊逻辑算法等尖端技术。经过超过 15 年的持续研究和对创新的专注&#xff0c;我们的 SDK 已成为市场上针对PDF、OCR、条形码、文档成像和各种格式最全面的…

Redis数据结构总结

Redis 是一款开源的&#xff0c;内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息代理。Redis 支持多种类型的数据结构&#xff0c;如字符串&#xff08;String&#xff09;、哈希&#xff08;Hashes&#xff09;、列表&#xff08;Lists&#xff09;、集合&…

C盘扩容遇到的问题(BitLocker解密、)

120G的C盘不知不觉的就满了&#xff0c;忍了好久终于要动手了。 尽管电脑-管理--磁盘管理里可以进行磁盘大小调整&#xff0c;但由于各盘都在用&#xff0c;不能够连续调整&#xff0c;所以选用DiskGenius。 # DiskGenius调整分区大小遇到“您选择的分区不支持无损调整容量” …

LINQ详解(查询表达式)

什么是LINQ&#xff1f; LINQ(语言集成查询)是将查询功能直接集成到C#中。数据查询表示简单的字符串&#xff0c;在编译时不会进行类型检查和IntelliSense(代码补全辅助工具)支持。 在开发中&#xff0c;通常需要对不同类型的数据源了解不同的查询语句&#xff0c;如SQL数据库…

老师们快看过来,这里有使用ChatGPT当助教的方法

最近OpenAI官方博客发布了一篇文章How teachers are using ChatGPT&#xff08;老师们如何使用ChatGPT&#xff09;&#xff0c;讲的是老师们如何在教学中使用ChatGPT&#xff0c;其中有几个例子挺好的&#xff0c;我转述一下&#xff0c;希望对你有用。 制定教案 第一个例子…

【大数据】Flink 详解(六):源码篇 Ⅰ

Flink 详解&#xff08;六&#xff09;&#xff1a;源码篇 Ⅰ 55、Flink 作业的提交流程&#xff1f;56、Flink 作业提交分为几种方式&#xff1f;57、Flink JobGraph 是在什么时候生成的&#xff1f;58、那在 JobGraph 提交集群之前都经历哪些过程&#xff1f;59、看你提到 Pi…

Android Aidl跨进程通讯(二)--异常捕获处理

学更好的别人&#xff0c; 做更好的自己。 ——《微卡智享》 本文长度为1623字&#xff0c;预计阅读5分钟 前言 上一篇《Android Aidl跨进程通讯的简单使用》中介绍了跨进程的通讯处理&#xff0c;在进程间的数据通过Aidl实现了交互&#xff0c;项目中经常会遇到Bug&#xff0c…