基于C++的OpenCV项目实战——零部件的自动光学检测

2023-11-14

基于C++的OpenCV项目实战——零部件的自动光学检测

一、背景

首先任务背景是AOI(自动光学检测)

最重要的目的在于:将前景和物体进行分割与分类;

场景示意图:

在这里插入图片描述

需要注意,在螺母的传送带上,需要有前光和背光,给物体打光才能够拍摄清晰的图像;


二、基础知识

首先分为以下几步:

1、噪声抑制(预处理)

2、背景移除(分割)

3、二值化

4、连通域、轮廓查找算法


  • 降噪算法:

​ 先使用中值滤波对椒盐噪声进行过滤,再使用高斯滤波对物体边缘进行模糊;

  • 背景移除

    首先有两种方案可以实现背景移除,也就是减法和除法;

在这里插入图片描述

  • 连通图检测计数

    首先连通域类型分为4路连通和8路连通:

在这里插入图片描述

使用连通图检测算法,可以将不连通的每个物体都用不同颜色划分出来;


三、代码实现

1、实现多窗口展示

如果想要多张图像展示在一个窗口中,也就是实现拼接图片的操作,使用Python代码实现起来可能比较便捷,C++代码需要定义一个类,并且实际编写也比较繁琐;

class Display {
private:
	int cols, rows, width, height;
	String title;
	vector<String> win_names;
	vector<Mat> images;
	Mat canvas;
public:
	Display(String t, int c, int r, int flags) :title(t), cols(c), rows(r) {
		height = 1080;
		width = 1920;
		namedWindow(title, flags);
		canvas = Mat(height, width, CV_8UC3);
		imshow(title, canvas);
	}

	int add_window(String win_name, Mat image, bool flag = true) {
		win_names.push_back(win_name);
		images.push_back(image);
		if (flag) {
			draw();
		}
		return win_names.size();
	}
	
    // 实现删除窗口
	int delete_window(String win_name) {
		int index = 0;
		for (const auto& it : win_names) {
			if (it == win_name) break;
			index++;
		}
		win_names.erase(win_names.begin() + index);
		images.erase(images.begin() + index);

		return win_names.size();
	}

	void draw() {
		canvas.setTo(Scalar(20, 20, 20));
		int single_width = width / cols;
		int single_height = height / rows;
		int max_win = win_names.size() > cols * rows ? cols * rows : win_names.size();
		
		int i = 0;
		auto iw = win_names.begin();
		for (auto it = images.begin(); it != images.end()&&i<max_win; it++,i++,iw++) {
			String win_name = *iw;
			Mat img = *it;

			int x = (single_width) * (i % cols);
			int y = (single_height)*floor(i * 1.0 / cols);
			Rect mask(x, y, single_width, single_height);

			rectangle(canvas, mask, Scalar(255, 255, 255), 9);

			Mat resized_img;
			resize(img, resized_img, Size(single_width, single_height));

			Mat sub_canvas(canvas, mask);
			if (resized_img.channels() == 1) {
				cvtColor(resized_img, resized_img, COLOR_GRAY2BGR);
			}
			resized_img.copyTo(sub_canvas);
            putText(sub_canvas, win_name, Point(50, 50), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 255), 3, LINE_AA);
		}
		imshow(title, canvas);
	}
};

// 使用智能指针
shared_ptr<Display> multi_window;

int main(int argc, char** argv)
{

	// 实现多窗口
	String total_path = "imgpath";
	String background_path = "imgpath";

	Mat abc = imread(total_path, 0);

	multi_window = make_shared<Display>("Review for all", 3, 2, WINDOW_NORMAL);
	multi_window->add_window("ABC", abc);
	multi_window->add_window("ABCC", abc);
    multi_window->delete_window("ABC");		// 也支持删除窗口
	multi_window->draw();

	waitKey(0);
	return 0;
}

在这里插入图片描述


2、降噪处理

采用中值滤波+高斯滤波结合的降噪方法:

Mat get_background(const Mat& bg){
    Mat img;
    medianBlur(bg,img,3);
    GaussianBlur(bg,img,Size(3,3),0);
    return img;
}

Mat smoothen_img(const Mat& noise_img){
    Mat img;
    medianBlur(noise_img,img,5);
    GaussianBlur(img,img,Size(3,3),0);
    return img;
}

3、背景去除

分为两种方式,一种为减法,一种为除法;

Mat remove_background_divide(Mat image, Mat background) {
	Mat tmp;
	Mat fg, bg;
	image.convertTo(fg, CV_32F);
	background.convertTo(bg, CV_32F);
	tmp = 1 - (fg / bg);
	tmp.convertTo(tmp, CV_8U, 255);
	return tmp;
}

Mat remove_background_minus(Mat image, Mat background) {
	return background - image;
}

在这里插入图片描述

从结果图上看,使用除法的方式能更好的保留白色部分的信息,因此选用除法的方式;


4、连通图实现

对二值化后的图像进行连通域划分,并且用随机颜色绘制到Mask图上;

void connection_check(Mat image) {
	Mat labels;
	int num = connectedComponents(image, labels);
	if (num <= 1) {
		cout << "No stuff detect!!" << endl;
		return;
	}
	else
	{
		cout << num << " objects detected!!" << endl;
	}

	Mat display = Mat::zeros(image.rows, image.cols, CV_8UC3);
	for (int i = 1; i < num; i++) {
		Mat mask = (labels == i);
		display.setTo(random_color_generator(seed), mask);
	}
	multi_window->add_window("Segment", display);
}

在这里插入图片描述


5、计算连通域面积

OpenCV中也自带了对面积区域的计算:

void connection_heavy_check(Mat image) {
	Mat labels, stats, centroids;
	int num =connectedComponentsWithStats(image, labels, stats, centroids);
	if (num <= 1) {
		cout << "No stuff detect!!" << endl;
		return;
	}
	else
	{
		cout << num << " objects detected!!" << endl;
	}
	Mat display = Mat::zeros(image.rows, image.cols, CV_8UC3);
	for (int i = 1; i < num; i++) {
		// 得到连通域的质心点
		Point2i pt(centroids.at<double>(i, 0), centroids.at<double>(i, 1));
		// 打印标签和连通域坐标和面积
		cout << "Stuff #" << i << ", Position: " << pt << " ,Area: " << stats.at<int>(i, CC_STAT_AREA) << endl;
		Mat mask = (labels == i);
		display.setTo(random_color_generator(seed), mask);
		stringstream ss;
		ss << stats.at<int>(i, CC_STAT_AREA);
		putText(display, ss.str(), pt, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 255, 255), 2);
	}
	multi_window->add_window("Segment more", display);
}

在这里插入图片描述


6、轮廓检测

实现对物体轮廓的检测;

void get_contour(Mat image) {
	vector<vector<Point>> contours;
	findContours(image, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	Mat display = Mat::zeros(image.rows, image.cols, CV_8UC3);
	if (contours.size() == 0) {
		cout << "No contour detect!!" << endl;
		return;
	}
	else
	{
		cout << contours.size() << " contour detected!!" << endl;
	}
	for (int i = 0; i < contours.size(); i++) {
		drawContours(display, contours, i, random_color_generator(seed), 2);
	}
	multi_window->add_window("CONTOURS", display);
}

在这里插入图片描述

从上结果图可知,检测到的轮廓并不包含内部轮廓,如果想检测所有轮廓应该将findContours函数中的类型参数改为RETR_LIST即可;

四、总结

本次项目中涉及的技术点如下:

  • 多窗口展示
  • 背景去除
  • 连通图的实现
  • 轮廓边缘检测

并且在实际的C++代码中,还涉及了智能指针等高阶知识;

工业质检项目作为视觉领域较为成熟的落地项目,其大部分都是基于深度学习的方式实现了,但如果能掌握一些OpenCV的方法,也可以在项目中起到优化效果的作用;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于C++的OpenCV项目实战——零部件的自动光学检测 的相关文章

  • 将运算符 << 添加到 std::vector

    我想添加operator lt lt to std vector
  • C# 方法重载决策不选择具体的泛型覆盖

    这个完整的 C 程序说明了这个问题 public abstract class Executor
  • 使用 CMake 时如何导出 Emscripten 中的 C 函数

    In 本教程 https emscripten org docs porting connecting cpp and javascript Interacting with code html interacting with code
  • 在 CPP 类中将 C 函数声明为友元

    我需要在 C 函数中使用类的私有变量 我正在做这样的事情 class Helper private std string name public std getName return name friend extern C void in
  • 转换 const void*

    我有一个函数返回一个const void 我想用它的信息作为char 我可以将它投射为 C 风格的罚款 char variable但是当我尝试使用reinterpret cast like reinterpret cast
  • 传递 constexpr 对象

    我决定给予新的C 14的定义constexpr旋转并充分利用它 我决定编写一个小的编译时字符串解析器 然而 我正在努力保持我的对象constexpr将其传递给函数时 考虑以下代码 include
  • java中如何重新初始化int数组

    class PassingRefByVal static void Change int pArray pArray 0 888 This change affects the original element pArray new int
  • 强制初始化模板类的静态数据成员

    关于模板类的静态数据成员未初始化存在一些问题 不幸的是 这些都没有能够帮助我解决我的具体问题的答案 我有一个模板类 它有一个静态数据成员 必须为特定类型显式实例化 即必须专门化 如果不是这种情况 使用不同的模板函数应该会导致链接器错误 这是
  • 如何在 C# Designer.cs 代码中使用常量字符串?

    如何在 designer cs 文件中引用常量字符串 一个直接的答案是在我的 cs 文件中创建一个私有字符串变量 然后编辑 Designer cs 文件以使用此变量 而不是对字符串进行硬编码 但设计者不喜欢这样抛出错误 我明白为什么这行不通
  • 什么是空终止字符串?

    它与什么不同标准 字符串 http www cplusplus com reference string string 字符串 实际上只是一个数组chars 空终止字符串是指其中包含空字符的字符串 0 标记字符串的结尾 不一定是数组的结尾
  • 是否使用 C# 数据集? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我对 C 中的数据集概念有点困惑 编码 ASP NET 站点 但这并不重要 在我的阅读中 我了解到它们 本质上 用作我的应用程序和我的
  • 从 C# 使用 Odbc 调用 Oracle 包函数

    我在 Oracle 包中定义了一个函数 CREATE OR REPLACE PACKAGE BODY TESTUSER TESTPKG as FUNCTION testfunc n IN NUMBER RETURN NUMBER as be
  • 不可变类与结构

    以下是类与 C 中的结构的唯一区别 如果我错了 请纠正我 类变量是引用 而结构变量是值 因此在赋值和参数传递中复制结构的整个值 类变量是存储在堆栈上的指针 指向堆上的内存 而结构变量作为值存储在堆上 假设我有一个不可变的结构 该结构的字段一
  • 在 C 中使用枚举而不是 #defines 作为编译时常量是否合理?

    在 C 工作了一段时间后 我将回到 C 开发领域 我已经意识到 在不必要的时候应该避免使用宏 以便让编译器在编译时为您做更多的工作 因此 对于常量值 在 C 中我将使用静态 const 变量或 C 11 枚举类来实现良好的作用域 在 C 中
  • 比较:接口方法、虚方法、抽象方法

    它们各自的优点和缺点是什么 接口方法 虚拟方法 抽象方法 什么时候应该选择什么 做出这一决定时应牢记哪些要点 虚拟和抽象几乎是一样的 虚方法在基类中有一个实现 可以选择重写 而抽象方法则没有 并且must在子类中被覆盖 否则它们是相同的 在
  • C++:为什么 numeric_limits 对它不知道的类型起作用?

    我创建了自己的类型 没有任何比较器 也没有专门化std numeric limits 尽管如此 由于某种原因 std numeric limits
  • WPF DataGrid / ListView 绑定到数组 mvvm

    我们假设你有 N 个整数的数组 表示行数的整数值 在模型中 该整数绑定到视图中的 ComboBox Q1 如何将数组 或数组的各个项目 绑定到 DataGrid 或 ListView 控件 以便 当您更改 ComboBox 值时 只有那么多
  • 了解 Lambda 表达式和委托 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我已经尝试解决这个问题很长一段时间了 阅读在线博客和文章 但到目前为止还没有成功 什么是代表 什么是 Lambda 表达式 两者的优点
  • 在 Win32 控制台应用程序中设置光标位置

    如何在 Win32 控制台应用程序中设置光标位置 最好 我想避免制作句柄并使用 Windows 控制台功能 我花了整个早上沿着那条黑暗的小巷跑 它产生的问题比它解决的问题还要多 我似乎记得当我在大学时使用 stdio 做这件事相对简单 但我
  • 无法将字符串文字分配给装箱的 std::string 向量

    这是我的类型系统的简化版本 include

随机推荐

  • Python爬虫 网页请求的异常处理

    网页请求的异常处理主要有两大类 urllib error URLError 用于捕获由urllib request产生的异常 使用reason属性返回错误原因 urllib error HTTPError 用于处理HTTP与HTTPS请求的
  • sql语句百万级千万级数据量分页

    在开发系统时难免会遇见分页的列表查询 针对小数据量我们可以查询的时间可以忽略不记 但针对百万级别千万级别的数据量时改怎么优化查询语句呢 该如果使用分页呢 下面总结几点 适量增加索引 在经常查询的字段上 尽量避免like in is null
  • Pycharm配置解释器(interpreter)

    关于pycharm编译器的解释器 网友朋友的分享 Pycharm配置 1 解释器 interpreter 详细了解PyCharm支持的4种Python Interpreter和配置方法 对大多数人而言就只需要 分清虚拟解释器和系统解释器 使
  • 二叉树采用二叉链表存储,求树的结点个数

    typedef struct BiTNode ElemType data struct BiTNode lchild rchild BiTNode BiTree void PrePrder BiTree T int num if T NUL
  • [leetcode 周赛 148] 1146 快照数组

    目录 1146 Snapshot Array 快照数组 描述 思路 代码实现 1146 Snapshot Array 快照数组 描述 实现支持下列接口的 快照数组 SnapshotArray SnapshotArray int length
  • nginx关于add_header的坑

    一 add header指令不会去重 nginx做反向代理时 如果后端返回的response中已经有该header头 则通过add header后会返回给客户端两个同样的header头 场景1 nginxA作为反向代理 nginxB作为we
  • 如何利用运营商大数据准确获取客户?

    在今天运营商的大数据准确捕捉客户的时代 我们似乎看到客户在我们面前若隐若现 但当我们伸手去抓他们时 我们发现他们很少 原因是什么 我们的客户之所以成为美丽的海市蜃楼 主要原因 还在于对客户的把握不够精准 什么是大数据准确性 大数据精准获客是
  • 【论文阅读】基于深度学习的时序异常检测——TimesNet

    系列文章链接 参考数据集讲解 数据基础 多维时序数据集简介 论文一 2022 Anomaly Transformer 异常分数预测 论文二 2022 TransAD 异常分数预测 论文三 2023 TimesNet 基于卷积的多任务模型 论
  • 线性相关与线性无关的定义与性质

    定义1 线性相关 K n K n K nK n Kn Kn 中向量组
  • Caffe训练过程:test_iter test_interval等概念

    转自 http blog csdn net iamzhangzhuping article details 49993899 先上一张图 大家很熟悉的一张图 首先说明一个概念 在caffe中的一次迭代iteration指的是一个batch
  • Webrtc从理论到实践六: Webrtc官方demo运行

    系列文章目录 Webrtc从理论到实践一 初识 Webrtc从理论到实践二 架构 Webrtc从理论到实践三 角色 Webrtc从理论到实践四 通信 Webrtc从理论到实践五 编译webrtc源码 文章目录 系列文章目录 操作步骤 总结
  • 语义分割模型

    1 FCN 1 通道数 21 的特征层 21 数据集类数20 背景1 每一个像素有21个通道 对21个通道进行softmax回归 之后就可以获得每一个像素的每一个类别的预测概率 因为可以确认像素概率最大的那一类 2 CNN中的最后通过全连接
  • Blender 骨骼子父级关系链接

    一 骨骼子父集关系链接 操作顺序 1 选中物体 2 选中骨骼 3 Ctrl P根据个人需求选择 多数情况会选择自动权重 如果选择顺序不同 Ctrl P出现的界面就不会显示权重选择 二 骨骼被物件遮挡 选中骨骼 在下图中的界面里 勾选在前面即
  • 【51单片机】:定时器的详解(包括对单片机定时解释、各类定时方式,以及中断方式)

    学习目标 51定时 计数器的详解 码字不易 如有帮助请收藏 点赞哦 学习内容 背景知识 了解一下对以后学习有帮助 前提 首先我们知道51单片机内部有21 26个特殊功能寄存器 P x口寄存器 P0 P1 P2 P3 数据指针寄存器 DP0H
  • STM32 GPIO 详解

    0 实验平台 基于STM32F407ZG 1 GPIO 简介 1 1 简介 GPIO全称 General Purpose Input Output 即通用输入输出端口 一般用来采集外部器件的信息或者控制外部器件工作 即输入输出 1 2 ST
  • 前端系列——jquery.i18n.properties前端国际化解决方案“填坑日记”

    前言 最近 新的平台还没有开发完成 原来的老项目又提出了新的需求 系统国际化 如果是前后端完全分离的开发模式 要做国际化 真的太简单了 有现成的解决方案 基于Node构建的时下热门的任何一种技术选型都有成熟的方案 比如 vue vue i1
  • delphi 通过TNetHTTPClient解析抖音无水印高清视频原理及解决X-Bogus签名验证2023-5-1

    一 杂谈 最近有很多热心网友反馈抖音去水印又不行了 之前是时不时被blocked 现在直接连内容都没有了 返回直接就是空了 我们今天简要给大家分析一下请求过程 附上delphi 源码 及生成签名验证 成功请求到json数据的解决方法 二 请
  • 损失函数:IoU、GIoU、DIoU、CIoU、EIoU、alpha IoU、SIoU、WIoU超详细精讲及Pytorch实现

    前言 损失函数是用来评价模型的预测值和真实值不一样的程度 损失函数越小 通常模型的性能越好 不同的模型用的损失函数一般也不一样 损失函数的使用主要是在模型的训练阶段 如果我们想让预测值无限接近于真实值 就需要将损失值降到最低 在这个过程中就
  • 苹果官网序列号查询

    苹果官网 https checkcoverage apple com cn zh locale zh CN 官换机要怎么鉴别是正品新机 https www zhihu com question 44779845
  • 基于C++的OpenCV项目实战——零部件的自动光学检测

    基于C 的OpenCV项目实战 零部件的自动光学检测 一 背景 首先任务背景是AOI 自动光学检测 最重要的目的在于 将前景和物体进行分割与分类 场景示意图 需要注意 在螺母的传送带上 需要有前光和背光 给物体打光才能够拍摄清晰的图像 二