C++?OpenCV實(shí)戰(zhàn)之零部件的自動(dòng)光學(xué)檢測(cè)
一、背景
首先任務(wù)背景是AOI(自動(dòng)光學(xué)檢測(cè))
最重要的目的在于:將前景和物體進(jìn)行分割與分類;
場(chǎng)景示意圖:

需要注意,在螺母的傳送帶上,需要有前光和背光,給物體打光才能夠拍攝清晰的圖像;
二、基礎(chǔ)知識(shí)
首先分為以下幾步:
1、噪聲抑制(預(yù)處理)
2、背景移除(分割)
3、二值化
4、連通域、輪廓查找算法
降噪算法
先使用中值濾波對(duì)椒鹽噪聲進(jìn)行過(guò)濾,再使用高斯濾波對(duì)物體邊緣進(jìn)行模糊;
背景移除
首先有兩種方案可以實(shí)現(xiàn)背景移除,也就是減法和除法;

連通圖檢測(cè)計(jì)數(shù)
首先連通域類型分為4路連通和8路連通:

使用連通圖檢測(cè)算法,可以將不連通的每個(gè)物體都用不同顏色劃分出來(lái);
三、代碼實(shí)現(xiàn)
1、實(shí)現(xiàn)多窗口展示
如果想要多張圖像展示在一個(gè)窗口中,也就是實(shí)現(xiàn)拼接圖片的操作,使用Python代碼實(shí)現(xiàn)起來(lái)可能比較便捷,C++代碼需要定義一個(gè)類,并且實(shí)際編寫(xiě)也比較繁瑣;
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();
}
// 實(shí)現(xiàn)刪除窗口
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)
{
// 實(shí)現(xiàn)多窗口
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、降噪處理
采用中值濾波+高斯濾波結(jié)合的降噪方法:
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;
}

從結(jié)果圖上看,使用除法的方式能更好的保留白色部分的信息,因此選用除法的方式;
4、連通圖實(shí)現(xiàn)
對(duì)二值化后的圖像進(jìn)行連通域劃分,并且用隨機(jī)顏色繪制到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、計(jì)算連通域面積
OpenCV中也自帶了對(duì)面積區(qū)域的計(jì)算:
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++) {
// 得到連通域的質(zhì)心點(diǎn)
Point2i pt(centroids.at<double>(i, 0), centroids.at<double>(i, 1));
// 打印標(biāo)簽和連通域坐標(biāo)和面積
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、輪廓檢測(cè)
實(shí)現(xiàn)對(duì)物體輪廓的檢測(cè);
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);
}
從上結(jié)果圖可知,檢測(cè)到的輪廓并不包含內(nèi)部輪廓,如果想檢測(cè)所有輪廓應(yīng)該將findContours函數(shù)中的類型參數(shù)改為RETR_LIST即可;
四、總結(jié)
本次項(xiàng)目中涉及的技術(shù)點(diǎn)如下:
- 多窗口展示
- 背景去除
- 連通圖的實(shí)現(xiàn)
- 輪廓邊緣檢測(cè)
并且在實(shí)際的C++代碼中,還涉及了智能指針等高階知識(shí);
工業(yè)質(zhì)檢項(xiàng)目作為視覺(jué)領(lǐng)域較為成熟的落地項(xiàng)目,其大部分都是基于深度學(xué)習(xí)的方式實(shí)現(xiàn)了,但如果能掌握一些OpenCV的方法,也可以在項(xiàng)目中起到優(yōu)化效果的作用;
到此這篇關(guān)于C++ OpenCV實(shí)戰(zhàn)之零部件的自動(dòng)光學(xué)檢測(cè)的文章就介紹到這了,更多相關(guān)C++ OpenCV光學(xué)檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Qt實(shí)現(xiàn)C/C++調(diào)用Matlab函數(shù)全過(guò)程
這篇文章給大家詳細(xì)介紹了基于Qt平臺(tái)實(shí)現(xiàn)C/C++調(diào)用Matlab函數(shù)全流程,文中通過(guò)圖文和代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01
C++ Eigen庫(kù)計(jì)算矩陣特征值及特征向量
這篇文章主要為大家詳細(xì)介紹了C++ Eigen庫(kù)計(jì)算矩陣特征值及特征向量,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
C語(yǔ)言運(yùn)算符深入探究?jī)?yōu)先級(jí)與結(jié)合性及種類
C語(yǔ)言運(yùn)算符號(hào)指的是運(yùn)算符號(hào)。C語(yǔ)言中的符號(hào)分為10類:算術(shù)運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、位操作運(yùn)算符、賦值運(yùn)算符、條件運(yùn)算符、逗號(hào)運(yùn)算符、指針運(yùn)算符、求字節(jié)數(shù)運(yùn)算符和特殊運(yùn)算符2022-05-05
C語(yǔ)言實(shí)現(xiàn)餐飲管理與點(diǎn)餐系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)餐飲管理與點(diǎn)餐系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01
C語(yǔ)言中你不知道的隱式類型轉(zhuǎn)換規(guī)則詳解
在C語(yǔ)言中,類型轉(zhuǎn)換的方式一般可分為隱式類型轉(zhuǎn)換和顯示類型轉(zhuǎn)換(也稱為強(qiáng)制類型轉(zhuǎn)換),其中隱式類型轉(zhuǎn)換由編譯器自動(dòng)進(jìn)行,不需要程序員干預(yù),本文給大家詳細(xì)介紹了C語(yǔ)言中隱式類型轉(zhuǎn)換規(guī)則,需要的朋友可以參考下2024-01-01

