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

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

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

相关文章

UMA 2 - Unity Multipurpose Avatar☀️一.让UMA角色动起来

文章目录 🟥 项目基础配置1️⃣UMA_DCS 预制体2️⃣创建 UMA 角色🟧 让UMA动起来1️⃣ 新建空场景,添加UMA_DCS预制体2️⃣配置 vBasicController_Template🟥 项目基础配置 1️⃣UMA_DCS 预制体 将 UMA_DCS 预制体放到场景中 2️⃣创建 UMA 角色 创建空物体,添加 Dy…

陪诊系统|陪诊软件开发|陪诊系统搭建功能

为了顺应不断变化的市场需求&#xff0c;有些行业慢慢销声匿迹&#xff0c;有些行业刚刚崭露头角&#xff0c;目前陪诊的市场需求也在逐渐扩大&#xff0c;陪诊小程序也随之到来&#xff0c;主要面向独居老人&#xff0c;孕妇&#xff0c;残障人士等等给予专业性的陪诊就医服务…

结构方程模型SEM、路径分析房价和犯罪率数据、预测智力影响因素可视化2案例...

原文链接&#xff1a;http://tecdat.cn/?p25044 在本文&#xff0c;我们将考虑观察/显示所有变量的模型&#xff0c;以及具有潜在变量的模型&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 1 简介 第一种有时称为“路径分析”&#xff0c;而后者有时称为“测…

成都睿趣科技:现在开一家抖音小店还来得及吗

随着社交媒体的迅猛发展&#xff0c;抖音已经成为了一个全球范围内广受欢迎的社交平台。在这个短视频应用上&#xff0c;人们分享着各种各样的内容&#xff0c;从搞笑段子到美食教程&#xff0c;再到时尚搭配和手工艺品制作。随着用户数量的不断增长&#xff0c;很多人都在思考…

【动手学深度学习】--文本预处理

文章目录 文本预处理1.读取数据集2.词元化3.词表4.整合所有功能 文本预处理 学习视频&#xff1a;文本预处理【动手学深度学习v2】 官方笔记&#xff1a;文本预处理 对于序列数据处理问题&#xff0c;在【序列模型】中评估了所需的统计工具和预测时面临的挑战&#xff0c;这…

180B参数的Falcon登顶Hugging Face,最好开源大模型使用体验

文章目录 使用地址使用体验test1:简单喜好类问题使用地址 https://huggingface.co/spaces/tiiuae/falcon-180b-demo 使用体验 相比Falcon-7b,Falcon-180b拥有1800亿的参数量,在智能问答领域做到了Top 1。在回答问题的深度和广度上都明显优于只有70亿参数量的Falcon-7b,并…

【计算机基础知识9】前端设计模式与常见类型

目录 一、前言 二、设计模式的基本概念和原则 三、创建型设计模式 四、结构型设计模式 五、行为型设计模式 六、MVC和MVVM框架中的设计模式 七、实际应用案例分析 一、前言 在软件开发领域&#xff0c;设计模式是一种解决常见问题的最佳实践&#xff0c;它可以帮助开发…

C++ std::pair and std::list \ std::array

std::pair<第一个数据类型, 第二个数据类型> 变量名 例如&#xff1a; std::pair<int, string> myPair; myPair.first;拿到第一个int变量 myPair.second拿到第二个string变量 std::pair需要引入库#include "utility" std::make_pair() 功能制作一个…

快速文件复制与删除工具,将复制时文件夹里的原文件删除掉

无论是工作还是生活&#xff0c;我们都离不开文件的复制和管理。然而&#xff0c;手动复制文件不仅费时费力&#xff0c;而且容易出错。现在&#xff0c;我们为您推荐一款快速文件复制与删除工具&#xff0c;让您的文件管理更加高效&#xff01; 首先&#xff0c;我们要进入文…

巨人互动|Google海外户Google关键词工具有哪些?

关键词是Google SEO网站流量的关键&#xff0c;也是Google SEO优化的核心。一个好的关键词工具可以为你提供Google SEO排名所需的一切数据。那么Google关键词工具有哪些&#xff1f;下面小编来给大家介绍一下吧&#xff01; 1、Google Trends Google Trends 可以看到特定品牌或…

macOS通过钥匙串访问找回WiFi密码

如果您忘记了Mac电脑上的WiFi密码&#xff0c;可以通过钥匙串访问来找回它。具体步骤如下&#xff1a; 1.打开Mac电脑的“启动台”&#xff0c;然后在其他文件中找到“钥匙串访问”。 2.运行“钥匙串访问”应用程序&#xff0c;点击左侧的“系统”&#xff0c;然后在右侧找到…

Greenplum执行SQL卡住的问题

问题 今天社区群里面一位同学反映他的SQL语句执行会hang住&#xff0c;执行截图如下。 分析 根据提示信息&#xff0c;判断可能是网络有问题&#xff0c;或者是跟GP使用UDP包有关系。 此同学找了网络检查的人确定网络没有问题&#xff0c;于是猜测跟UDP包有关。 参考文章ht…

Mysql 性能分析(慢日志、profiling、explain)、读写分离(主从架构)、分库分表(垂直分库、垂直分表、水平分表)

查看系统性能参数 一条sql查询语句在执行前&#xff0c;需要确定查询执行计划&#xff0c;如果存在多种执行计划的话&#xff0c;mysql会计算每个执行计划所需要的成本&#xff0c;从中选择 成本最小的一个作为最终执行的执行计划 想要查看某条sql语句的查询成本&#xff0c;可…

CS420 课程笔记 P5 - 内存编辑 数据类型

文章目录 IntroductionData typesBooleansNegative numbers (Signed integers)Floating-point numbers (fractional numbers) Unknown value scansHealth findingFloat finding (Player position hack / Teleport hack) Additional things Introduction 这节课将结束数据类型并…

基于Java+SpringBoot+Vue前后端分离火锅店管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

cadence后仿真/寄生参数提取/解决pin口提取不全的问题

post-simulation设置顺序与规则 1.Rules 设置 2.inputs设置 3.outputs设置 4.PEX 设置 会出现错误1&#xff0c;后有解决方案 第一步 :Netlist 第二步&#xff1a;LVS 5.RUN PEX 先RUN&#xff0c;后按照图中1 2 3步骤操作 点击OK之后&#xff0c;显示Calibre信息&#xff…

您的密码是如何落入坏人之手的?

对于我们大多数人来说&#xff0c;密码只是无数在线服务最常用的身份验证方法。但对于网络犯罪分子而言&#xff0c;它的意义远不止于此——进入他人生活的捷径、至关重要的作案工具以及可以出售的商品。 对于我们大多数人来说&#xff0c;密码只是无数在线服务最常用的身份验证…

C#学习 - 初识类与名称空间

类&#xff08;class&#xff09;& 名称空间&#xff08;namespace&#xff09; 类是最基础的 C# 类型&#xff0c;是一个数据结构&#xff0c;是构成程序的主体 名称空间以树型结构组织类 using System; //前面的using就是引用名称空间 //相当于C语言的 #include <..…

[Linux]动静态库

[Linux]动静态库 文章目录 [Linux]动静态库见一见库存在库的原因编写库模拟编写静态库模拟使用静态库模拟编写动态库模拟使用静态库 库的加载原理静态库的加载原理动态库的加载原理 库在可执行程序中的编址策略静态库在可执行程序中的编址策略动态库在可执行程序中的编址策略 见…

网络安全架构:建立安全架构方法的指导框架

01 关键发现 ■ 架构框架使用集体见解来创建最佳实践&#xff0c;指导用户考虑组织风险和业务环境。这些方法的改编和定制&#xff0c;将帮助组织从中获得最佳价值。 ■ 方法论提供了一种系统工程方法&#xff0c;使用业务输入和期望&#xff0c;来创建可重复、可跟踪&#xf…