目录一、背景二、基础知识三、代码实现1、实现多窗口展示2、降噪处理3、背景去除4、连通图实现5、计算连通域面积6、轮廓检测四、总结一、背景 首先任务背景是AOI(自动光学检测) 最重
首先任务背景是AOI(自动光学检测)
最重要的目的在于:将前景和物体进行分割与分类;
场景示意图:
需要注意,在螺母的传送带上,需要有前光和背光,给物体打光才能够拍摄清晰的图像;
首先分为以下几步:
1、噪声抑制(预处理)
2、背景移除(分割)
3、二值化
4、连通域、轮廓查找算法
降噪算法
先使用中值滤波对椒盐噪声进行过滤,再使用高斯滤波对物体边缘进行模糊;
背景移除
首先有两种方案可以实现背景移除,也就是减法和除法;
连通图检测计数
首先连通域类型分为4路连通和8路连通:
使用连通图检测算法,可以将不连通的每个物体都用不同颜色划分出来;
如果想要多张图像展示在一个窗口中,也就是实现拼接图片的操作,使用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;
}
采用中值滤波+高斯滤波结合的降噪方法:
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;
}
分为两种方式,一种为减法,一种为除法;
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;
}
从结果图上看,使用除法的方式能更好的保留白色部分的信息,因此选用除法的方式;
对二值化后的图像进行连通域划分,并且用随机颜色绘制到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);
}
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);
}
实现对物体轮廓的检测;
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的方法,也可以在项目中起到优化效果的作用;
到此这篇关于C++ OpenCV实战之零部件的自动光学检测的文章就介绍到这了,更多相关C++ OpenCV光学检测内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: C++ OpenCV实战之零部件的自动光学检测
本文链接: https://lsjlt.com/news/167513.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0