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

news2025/1/26 15:38:32

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、将待检测图像与亮、暗阈值图像逐像素比较,设定阈值,超出阈值部分的即为缺陷

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++二叉树遍历

目录 二叉树节点结构&#xff1a; 1.1 前序遍历&#xff08;Preorder Traversal&#xff09;&#xff1a; 递归实现&#xff08;preorderRecursive函数&#xff09;&#xff1a;首先访问当前节点&#xff0c;然后递归遍历左子树&#xff0c;最后递归遍历右子树。这种遍历方式…

【Mycat 2】详解分库分表设计方案及实操测试(二)

文章目录 4. 分表、不分实例、不分库4.1 数据分片映射关系4.2 测试过程5. 分实例、分表、不分库5.1 分库键和分表键相同5.1.1 数据分片映射关系5.1.2 测试过程5.2 分库键和分表键不相同5.2.1 数据分片映射关系5.2.2 测试过程6. 不分片6.1 普通表6.1.1 数据映射关系6.1.2 测试过…

Java智慧工地信息化管理平台源码,依托计算机信息、网络通讯、物联网、系统集成及云计算技术建立

Java智慧工地源码 智慧工地APP源码 系统定义&#xff1a; 智慧工地信息化管理平台是依托计算机信息、网络通讯、物联网、系统集成及云计算技术&#xff0c;通过数据采集、信息动态交互、智能分析&#xff0c;建立起来的一套集成的项目建设综合管理系统。实现项目管理信息化、网…

自定义TimeLine实现卡拉OK轨

系列文章目录 自定义TimeLine 自定义TimeLine 系列文章目录前言正文UI部分代码部分Data&#xff08;数据&#xff09;Clip&#xff08;片段&#xff09;Track&#xff08;轨道&#xff09;Mixer&#xff08;混合&#xff09;被控制物体 总结 前言 自定义TimeLine实际上就是自定…

搭建STM32F407的Freertos系统(基于STM32CubeMX)

本人长期开发Linux、Windows上应用软件&#xff0c;一直以来MCU开发有所接触&#xff0c;但较少&#xff08;最近项目需要&#xff0c;小公司么&#xff0c;都得会&#xff0c;被逼的&#xff09;&#xff0c;好在有STM32CubeMX这样工具&#xff0c;貌似就是我想要的工具。 本次…

C++ sizeof求类型大小

注意内存对齐 struct Stu {int id;char sex;float hight; }; cout<<sizeof(Stu)<<endl; 会输出什么&#xff1f; 字节对齐原则:在系统默认的对齐方式下&#xff1a;每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍&#xff0c;且最终…

Level-based Foraging 多智能体游戏仿真环境

游戏场景测试 参考链接&#xff1a; https://kgithub.com/semitable/lb-foraging

信息化发展16

计算机网络 从网络的作用范围可将网络类别划分为个人局域网&#xff08; Per sona l Area Net work,PAN) &#xff1e;局域网C Local Area Net work, LAN ) &#xff1e; 城域网&#xff08; Metropoli tan Areaetwork , MAN &#xff09; 、广域网&#xff08; Wide Area Net…

230902-部署Gradio到已有FastAPI及服务器中

1. 官方例子 run.py from fastapi import FastAPI import gradio as grCUSTOM_PATH "/gradio"app FastAPI()app.get("/") def read_main():return {"message": "This is your main app"}io gr.Interface(lambda x: "Hello, …

基于缓冲原理计算轨迹相似度

目录 前言预备知识思路与核心代码优缺点分析数值实验参考文献 前言 接上文&#xff0c;我们已经知道如何利用夹角余弦来计算两条轨迹的相似度&#xff0c;也知道其中优势和劣势&#xff0c;夹角余弦方法作为一个基础的baseline有其存在的价值&#xff0c;很多学者也提出了各式…

前端基础4——jQuery

文章目录 一、基本了解1.1 导入jQuery库1.2 基本语法1.3 选择器 二、操作HTML2.1 隐藏和显示元素2.2 获取与设置内容2.3 获取、设置和删除属性2.4 添加元素2.5 删除元素2.6 设置CSS样式 三、jQuery Ajax3.1 基本语法3.2 回调函数3.3 常用HTTP方法3.4 案例一3.4.1 准备工作3.4.2…

SpringBoot完整项目部署流程(软件安装-前后端部署)

SpringBoot完整项目部署流程 安装Jdk 使用XTFP工具将jdk的二进制发布包上传到Linux 解压安装包 tar -zxvf jdk-8u171-linux-x64.tar.gz -C /usr/local解压完毕 配置环境变量&#xff0c;使用vim命令修改 /etc/profile文件&#xff0c;在文件末尾加入如下配置 JAVA_HOME/usr…

LeetCode 45题:跳跃游戏

题目 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返回到达 nums[n - …

ctfshow—萌新赛—给她

0x00 前言 CTF 加解密合集CTF Web合集网络安全知识库 文中工具皆可关注 皓月当空w 公众号 发送关键字 工具 获取 0x01 题目 0x02 Write Up 首先看到访问页面&#xff0c;正常情况下这种都是sql注入&#xff0c;尝试发现被转义了 尝试无果之后&#xff0c;扫描目录&#xff…

zookeeper 理论合集

目录 系统背景 集群结构 多个节点之间的角色 节点的状态 为什么引入 Observer 存储结构 ZNode 节点结构 ZNode 创建类型 内存数据存储 数据持久化 zookeeper 的容量大小 数据同步 消息广播 崩溃恢复 如何保证顺序一致性 核心流程 Leader 选举流程 脑裂问题 …

Python基础篇(17):模块与包

一、as 关键字的使用 1、as 关键字的作用&#xff1a;给导入的模块取别名 import 测试1 as Test_1 import 测试2 as Test_2Test_1.say_hello() Test_2.say_hello() 二、if __name__ __main__ 1、作用 测试当前模块所编写的代码块&#xff0c;根据业务自主选择需要运行的代…

李宏毅hw1_covid19预测_代码研读+想办法降低validation的loss(Kaggle目前用不了)

1.考虑调整这个neural network的结构尝试让这个loss降低 &#xff08;1&#xff09;Linear(inputdim,64) - ReLU-Linear(64,1), loss0.7174 &#xff08;2&#xff09;Linear(inputdim,64) - ReLU-Linear(64,64) -ReLU-Linear(64,1),loss 0.6996 &#xff08;3&#xff09;这…

spring boot + Consul 示例 (Kotlin版)

文章目录 1.docker 安装consul2.创建基于springboot的client2.1 依赖版本2.2 pom.xml2.3 启动类2.4 application.properties 3 搭建完成4. 总结 1.docker 安装consul docker-compose.yaml version: "3"services:consul:image: consul:1.4.4container_name: consule…

DevEco Studio 介绍、下载及安装

DevEco Studio 简介 HUAWEI DevEco Studio面向App、HAG快服务、IoT智能硬件设备3类开发者&#xff0c;提供设计、编码、编译、调测和云端测试等端到端一站式服务。一次开发&#xff0c;多端部署&#xff1a;支持18N全场景泛终端软件应用和服务开发开放能力一站集成&#xff1a…

5 大虚拟数字人工具:视频内容创作的未来

人工智能&#xff08;AI&#xff09;给视频内容创作领域带来了一场革命。这一领域的显着进步之一是人工智能生成的会说话的化身的出现&#xff0c;它已经成为制作高质量视频的游戏规则改变者&#xff0c;而无需专业演员或昂贵的视频编辑软件。在这篇博文中&#xff0c;我们将深…