本周有機(jī)會接觸了一點opnev, 在此做一下記錄, 最終以框選出下圖箱子為目的(圖片箱子為相機(jī)實拍結(jié)果,曝光有點低,會有億點點暗 ), 本文會拆解步驟并附上圖片, 完整的源碼在最后.PS:本文參考了好多大佬分享的理論知識, 在此先感謝大佬的分享~~

首先是梳理一下流程, 下圖是本次圖片處理的大概流程和部分效果圖以及部分會用到的算子~~

1.圖像讀取
圖像讀取可以直接使用如下模塊讀取直接路徑的圖片:
img1 = cv::imread("C:/Users/Jiang/Desktop/data/1.png");
但如果預(yù)處理圖片較多, 需要從文件夾循環(huán)讀取處理的話可以用如下模塊,只需更改文件夾地址以及文件命名格式就行, 這里我文件夾里的圖片名字均為"1.png"這樣的格式:
for (int ii = 1; ii < 11; ii++)
{
vector<cv::Mat> images;
string name = cv::format("C:/Users/Jiang/Desktop/data3/%d.png", ii);
cv::Mat img1 = cv::imread(name);
if (img1.empty())
{
printf("沒找到圖片");
return -1;
}
images.push_back(img1);
//如果是循環(huán)讀取,后面的處理就放在這個位置,不要出花括號哦~
}
2.提取需要處理的roi區(qū)域
這里的示例是拍照空間區(qū)域中存在一個托盤, 托盤上方有預(yù)提取的箱子, 所以需要建立托盤的roi區(qū)域, 之后只單獨對這個區(qū)域進(jìn)行處理, 這樣可以排除干擾項~
cv::resize(img1, img2, cv::Size(img1.cols / 7, img1.rows / 7), 0, 0, cv::INTER_NEAREST);//這里是因為圖片太大了,所以等比例縮小了一下面積
//通過roi區(qū)域篩選出托盤位置
cv::Mat roi(img2, cv::Rect(190, 140, 280, 250));
3.灰度處理
這一步轉(zhuǎn)成灰度圖, 并進(jìn)行閾值提取,方便之后提取
cv::cvtColor(roi, g_grayImage, cv::COLOR_BGR2GRAY);
cv::threshold(g_grayImage, img3, 12, 255, cv::THRESH_BINARY);
4.灰度處理
這里的腐蝕是為了將閾值分割之后的部分噪聲去除, 膨脹是為了還原上一步被刪除了的特征點,偷個懶用上面的圖了哈~其實這一步就和直接用閉運算是一樣的, 但是為了能看出過程的變換我就拆分開了.

//定義腐蝕和膨脹的結(jié)構(gòu)化元素和迭代次數(shù)
element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
int iteration = 2;
int iteration2 = 1;
//腐蝕
cv::morphologyEx(img3, img3, cv::MORPH_ERODE, element, cv::Point(-1, -1), iteration);
cv::imshow("1", roi);
cv::imshow("腐蝕后", img3);
//膨脹
cv::morphologyEx(img3, img3, cv::MORPH_DILATE, element, cv::Point(-1, -1), iteration2);
cv::imshow("膨脹后", img3);
5.提取連通區(qū)域并刪除其中面積較小的區(qū)域
// 提取連通區(qū)域,并剔除小面積聯(lián)通區(qū)域
std::vector<std::vector<cv::Point>> contours;
cv::findContours(img3, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
contours.erase(std::remove_if(contours.begin(), contours.end(), [](const std::vector<cv::Point>& c)
{
return cv::contourArea(c) < 150;
}), contours.end());
// 顯示圖像
img3.setTo(0);
cv::drawContours(img3, contours, -1, cv::Scalar(255), cv::FILLED);
cv::imshow("刪除了小面積區(qū)域", img3); //cv::waitKey(0);
6.篩選出面積較大的區(qū)域部分 并繪制矩形 框選出箱子輪廓
這一塊的邏輯就是第一次篩選掉小面積區(qū)域后, 對剩下的區(qū)域再進(jìn)行一次篩選,這次篩選面積較大的區(qū)域, 并對每一個面積大的區(qū)域進(jìn)行單獨繪制出輪廓, 并將這個輪廓點集用minAreaRect算子, 輸入點集輸出四個點的坐標(biāo), 并將這四個點用直線連接起來, 就可以得到結(jié)果了~

cv::Mat delet(img3.size(), img3.type(), cv::Scalar(0, 0, 0));;
cout << "剔除小區(qū)域后的輪廓個數(shù):" << contours.size();
for (int tmp = 0; tmp < contours.size(); tmp++)
{
if (cv::contourArea(contours[tmp]) > 3000)
{
cout << "大輪廓的編號是" << tmp;
cout << "此時的面積為" << cv::contourArea(contours[tmp]);
cv::drawContours(delet, contours, tmp, cv::Scalar(255), cv::FILLED);
cv::Canny(delet, mid, 0, 18, 3);
cv::bitwise_not(mid, bitwise);
// 用Canny算子檢測邊緣 閾值1和閾值2兩者中比較小的值用于邊緣連接,較大的值用來控制邊緣的初始段
Canny(bitwise, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);
// 尋找輪廓
findContours(g_cannyMat_output, g_vContours, g_vHierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
// 繪出輪廓
cv::Mat drawing = cv::Mat::zeros(g_cannyMat_output.size(), CV_8UC3);
for (int i = 0; i < g_vContours.size(); i++)
{
cv::Scalar color = cv::Scalar(255, 182, 193);
drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, cv::Point());
}
cv::imshow("drawidrawingdrawingng", drawing);
vector<cv::Point> tempPoint; // 點集
// 將所有點集存儲到tempPoint
for (int k = 0; k < g_vContours.size(); k++)
{
for (int m = 0; m < g_vContours[k].size(); m++)
{
tempPoint.push_back(g_vContours[k][m]);
}
}
//對給定的 2D 點集,尋找最小面積的包圍矩形
cv::RotatedRect box = minAreaRect(cv::Mat(tempPoint));
cv::Point2f vertex[4];
box.points(vertex);
//繪制出最小面積的包圍矩形
for (int i = 0; i < 4; i++)
{
cv::line(roi, vertex[i], vertex[(i + 1) % 4], cv::Scalar(100, 200, 211), 1, cv::LINE_AA);
}
cv::imshow("最終提取結(jié)果", roi);
cv::waitKey(0);
string Img_Name = "C:\\Users\\Jiang\\Desktop\\result\\" + to_string(k) + ".png";
cv::imwrite(Img_Name, roi);//這里是按照固定路勁保存結(jié)果圖片,下標(biāo)從0開始,如果重復(fù)了會覆蓋
k++;
delet = cv::Scalar(0, 0, 0);//這里是將處理過的圖片閾值賦為0,即為黑色,不然處理過的圖片還會殘留在這里
}
}
7.全部源碼
#include <iostream>
#include<opencv2\opencv.hpp>
#include "testOpenCV346.h"
#include <memory>
#include <opencv2/highgui/highgui.hpp>
#include <vector>
#include <algorithm>
using namespace std;
cv::Mat g_srcImage,img1,img2,img3, img4, element, element2, result, result2,erzhi,mid,g_grayImage,kai, bitwise,morph;//這里有一些是定義了其他函數(shù)用的,這里沒有刪哦
int g_nThresh = 13;
cv::Mat g_cannyMat_output;
vector<vector<cv::Point>> g_vContours; //向量內(nèi)每個元素保存了一組由連續(xù)的Point點構(gòu)成的點的集合的向量,每一組Point點集就是一個輪廓。有多少輪廓,向量contours就有多少元素
vector<cv::Vec4i> g_vHierarchy; //“向量內(nèi)每一個元素包含了4個int型變量”的向量分 別表示第i個輪廓的后一個輪廓、前一個輪廓、父輪廓、內(nèi)嵌輪廓的索引編號。
int main(int argc, char** argv)
{
std::shared_ptr<testOpenCV346> pMyOpenCv = std::make_shared<testOpenCV346>();
//for (int ii = 1; ii < 11; ii++)
//{
// vector<cv::Mat> images;
// string name = cv::format("C:/Users/Jiang/Desktop/data3/%d.png", ii);
// cv::Mat img1 = cv::imread(name);
// if (img1.empty())
// {
// printf("沒找到圖片");
// return -1;
// }
// images.push_back(img1);
// 加載源圖像,這里采用了固定路徑,如果要循環(huán),就用上面的for,記得下面的花括號打開就行
img1 = cv::imread("C:/Users/Jiang/Desktop/data/1.png");
cv::resize(img1, img2, cv::Size(img1.cols / 7, img1.rows / 7), 0, 0, cv::INTER_NEAREST);
//通過roi區(qū)域篩選出托盤位置
cv::Mat roi(img2, cv::Rect(190, 140, 280, 250));
cv::Mat roi1(img2, cv::Rect(190, 160, 261, 210));
//轉(zhuǎn)成灰度并模糊化降噪
cv::cvtColor(roi, g_grayImage, cv::COLOR_BGR2GRAY);
cv::threshold(g_grayImage, img3, 12, 255, cv::THRESH_BINARY);
cv::imshow("閾值之后", img3);
//cv::waitKey(0);
//定義腐蝕和膨脹的結(jié)構(gòu)化元素和迭代次數(shù)
element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
int iteration = 2;
int iteration2 = 1;
//腐蝕
cv::morphologyEx(img3, img3, cv::MORPH_ERODE, element, cv::Point(-1, -1), iteration);
cv::imshow("1", roi);
cv::imshow("腐蝕后", img3);
//cv::waitKey(0);
cv::morphologyEx(img3, img3, cv::MORPH_DILATE, element, cv::Point(-1, -1), iteration2);
cv::imshow("膨脹后", img3);
//cv::waitKey(0);
// 提取連通區(qū)域,并剔除小面積聯(lián)通區(qū)域
std::vector<std::vector<cv::Point>> contours;
cv::findContours(img3, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
contours.erase(
std::remove_if(contours.begin(), contours.end(), [](const std::vector<cv::Point>& c)
{
return cv::contourArea(c) < 150;
})
, contours.end());
// 顯示圖像
img3.setTo(0);
cv::drawContours(img3, contours, -1, cv::Scalar(255), cv::FILLED);
cv::imshow("刪除了小面積區(qū)域", img3); //cv::waitKey(0);
//cv::waitKey(0);
cv::Mat delet(img3.size(), img3.type(), cv::Scalar(0, 0, 0));;
cout << "剔除小區(qū)域后的輪廓個數(shù):" << contours.size();
for (int tmp = 0; tmp < contours.size(); tmp++)
{
if (cv::contourArea(contours[tmp]) > 3000)
{
cout << "大輪廓的編號是" << tmp;
cout << "此時的面積為" << cv::contourArea(contours[tmp]);
cv::drawContours(delet, contours, tmp, cv::Scalar(255), cv::FILLED);
cv::Canny(delet, mid, 0, 18, 3);
cv::bitwise_not(mid, bitwise);
// 用Canny算子檢測邊緣 閾值1和閾值2兩者中比較小的值用于邊緣連接,較大的值用來控制邊緣的初始段
Canny(bitwise, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);
// 尋找輪廓
findContours(g_cannyMat_output, g_vContours, g_vHierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
// 繪出輪廓
cv::Mat drawing = cv::Mat::zeros(g_cannyMat_output.size(), CV_8UC3);
for (int i = 0; i < g_vContours.size(); i++)
{
cv::Scalar color = cv::Scalar(255, 182, 193);
drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, cv::Point());
}
cv::imshow("drawidrawingdrawingng", drawing);
//cv::waitKey(0);
vector<cv::Point> tempPoint; // 點集
// 將所有點集存儲到tempPoint
for (int k = 0; k < g_vContours.size(); k++)
{
for (int m = 0; m < g_vContours[k].size(); m++)
{
tempPoint.push_back(g_vContours[k][m]);
}
}
//對給定的 2D 點集,尋找最小面積的包圍矩形
cv::RotatedRect box = minAreaRect(cv::Mat(tempPoint));
cv::Point2f vertex[4];
box.points(vertex);
//繪制出最小面積的包圍矩形
for (int i = 0; i < 4; i++)
{
cv::line(roi, vertex[i], vertex[(i + 1) % 4], cv::Scalar(100, 200, 211), 1, cv::LINE_AA);
}
cv::imshow("最終提取結(jié)果", roi);
cv::waitKey(0);
string Img_Name = "C:\\Users\\Jiang\\Desktop\\result\\" + to_string(k) + ".png";
cv::imwrite(Img_Name, roi);//這里是按照固定路勁保存結(jié)果圖片,下標(biāo)從0開始,如果重復(fù)了會覆蓋
k++;
delet = cv::Scalar(0, 0, 0);//這里是將處理過的圖片閾值賦為0,即為黑色,不然處理過的圖片還會殘留在這里
}
}
//cv::waitKey(0);
//}
cout << "任務(wù)完成咯~`";
return(0);
}
至此, 本次分享的箱子提取過程完結(jié), 圖片處理其實很吃圖片的像素分布情況 ( 是否有過亮過暗, 是否反光, 是否有噪聲, 是否清晰等 ) , 比如本次圖片就是屬于較暗的情況, 在閾值分割的時候就很頭疼, 用了自適應(yīng)閾值和直方圖閾值分割的方式, 但效果并不是很理想 ,最終還是手動給了閾值, 而且干擾項也蠻多的, 處理方式還是比較簡單和基礎(chǔ), 最后再附一張輪廓檢測的結(jié)果圖 , 效果其實也還行 . 比如圖片17 , 原圖是三個箱子堆疊在一起 , 但是輪廓補(bǔ)全后還是把箱子輪廓框選出來了.文章來源:http://www.zghlxwxcb.cn/news/detail-471540.html

這里單獨提一下,在VS的"工具"→"拓展和跟新"里可以找到"Image Watch 2017"插件,可以用來查看圖片的具體像素點,在閾值處理的時候會很有用文章來源地址http://www.zghlxwxcb.cn/news/detail-471540.html

到了這里,關(guān)于OPENCV C++圖像提取,圖像處理,roi,閾值分割,連通區(qū)域篩選,邊緣檢測(以箱子邊緣框選為例)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!