若該文爲原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105843092
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究
目錄
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中...(點擊傳送門)
OpenCV開發專欄(點擊傳送門)
OpenCV開發筆記(四十九):紅胖子8分鐘帶你深入瞭解輪廓識別(圖文並茂+淺顯易懂+程序源碼)
前言
紅胖子來也!!!
前面學了很多概念性的東西,本章開始開始做點實際的東西,車牌識別中,需要將車牌分離出來,一種方式是需要其輪廓,然後將其摳出來,再做其他操作。
Demo
輪廓
概述
輪廓可以理解爲是一系列點包圍了一個區域,它將一些列邊界包圍起來,形成的一個區域。
先通過濾波、閾值化的操作,然後尋找輪廓,定位到識別的物體的區域,這樣可以將區域標記出來,摳圖可以進而後續的二次處理。
原理
(注意:輪廓查找的算法有很多,本篇章使用cv的函數findContours(),所以詳解他的原理。)
識別過程如下:
查找輪廓函數原型
void findContours( InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point());
void findContours( InputOutputArray image,
OutputArrayOfArrays contours,
int mode,
int method,
Point offset = Point());
- 參數一:InputOutputArray類型的image,一般爲mat,輸入圖像,需要爲單通道圖像,圖像的非0像素被視爲1,0像素被保留爲9,所以圖像爲二進制圖像;
- 參數二:OutputArrayOfArrays類型的contours,檢測到的輪過,函數調用後,結果存儲,每個輪廓存儲爲一個點向量,即用point類型的vector表示。
- 參數三:OutputArray類型的hierarchy,可選的輸出向量,包含圖像的拓撲信息,其作爲輪廓數量的表示,包含了許多元素。每個輪廓contours[i]對應4個hierarchy元素hierarchy[i][0]~ hierarchy[i][3],分別表示後一個輪廓、前一個輪廓、父林廓、內嵌輪廓的是索引編號。如果沒有對應項,對應的hierarchy[i]值設爲負數。
- 參數四:int類型的mode, 輪廓檢索模式,取值如下:
序號 |
枚舉 |
值 |
描述 |
1 |
RETR_EXTERNAL |
0 |
僅檢索最外層輪廓。 |
2 |
RETR_LIST |
1 |
檢索所有輪廓,而不建立任何層次關係 |
3 |
RETR_CCOMP |
2 |
檢索所有輪廓並將其組織爲兩級層次結構。在頂端級別,組件有外部邊界。在第二層,有洞的邊界。如果在連接組件的孔內有另一個輪廓,則仍然被放在最高層。 |
4 |
RETR_TREE |
3 |
檢索所有輪廓並重建嵌套輪廓的完整層次結構 |
5 |
RETR_FLOODFILL |
4 |
輸入圖像應該是連接的組件或填充功能的結果 |
- 參數五:int類型的method,輪廓的近似辦法,如下表:
序號 |
枚舉 |
值 |
描述 |
1 |
CHAIN_APPROX_NONE |
0 |
存儲所有輪廓點。也就是說,任何2個後續點(x1,y1)和輪廓的(x2,y2)將是水平、垂直或對角線鄰接,即,max(abs(x1-x2),abs(y2-y1))==1。 |
2 |
CHAIN_APPROX_SIMPLE |
1 |
壓縮水平、垂直和對角線線段,只保留其端點 |
3 |
CHAIN_APPROX_TC89_L1 |
2 |
應用TehChin89近似算法的一種風格 |
4 |
CHAIN_APPROX_TC89_KCOS |
3 |
應用TehChin89近似算法的一種風格 |
- 參數六:Point類型的offset,每個輪廓點的可選偏移量,有默認值Point()。瑞ROI圖像中找出的輪廓,並要在整個圖像中進行分析師,這個參數便可派上用場。
繪製輪廓函數原型
void drawContours( InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point() );
- 參數一:InputOutputArray類型的image,目標圖像,填 Mat 類的對象即可 。
- 參數二:InputArrayOfArrays類型的contours,所有的輸入輪廓 。 每個
輪廓存儲爲 一個點 向 量 ,即用 pOlnt 類型 的 vector 表示。 - 參數三:int類型的contourIdx,輪廓繪製的指示變量 。 如果其爲負值,
則繪製所有輪廓 。 - 參數四:Scalar類型的color,輪廓的顏色
- 參數五:int類型的thickness,輪廓線條的粗細度,有默認值1。如果其爲負值(如thickness=cv_filled ),便會繪製在輪廓的內部。可選爲FlLLED宏(OpenCV2版爲CV_FILLED)。
- 參數六:int類型的lineType,線條的類型,有默認值8。取值類型如表:
- 參數七:InputArray類型的hierarchy,可選的層次結構信息,有默認值noArray();
- 參數八:int類型的maxLevel,表示用於繪製輪廓的最大等級,有默認值INT_MAX;
- 參數九:Point類型的offset,可選的輪廓偏移參數,用指定的偏移且offset=(dx,dy)偏移需要繪製的輪廓,有默認值Point();
分割車牌的步驟
步驟一:濾波
(略,本篇章主要是輪廓,車牌識別),爲了避免一些簡單的噪聲,在這裏使用了自適應流行濾波器進行濾波。
參照博文《OpenCV開發筆記(三十五):紅胖子8分鐘帶你深入瞭解ximgproc擴展模塊中的自適應流行濾波器(圖文並茂+淺顯易懂+程序源碼)》
步驟二:閾值化
使用合適的方式進行過濾,在這裏先轉成灰度圖,然後使用基礎閾值化,自己調整閾值化,達到一個合適的值。
參照博文《OpenCV開發筆記(二十八):帶你學習圖像識別之閾值化》
步驟三:尋找輪廓
使用輪過查找,查找出輪廓,得到輪廓信息,是本篇所關注的。
步驟四:分割輪廓
得到輪廓後,對圖像進行分割(摳圖),其實輪廓已經得出了基本的圖像輪廓信息,摳圖然後把該部分最外層輪廓摳出來就是數字和字母了還有一些雜質。
可以分爲兩步,先摳出來整張圖的這個部分(一整張圖大小,非摳圖部分爲黑色),然後在進行一次裁剪,得到實際圖像的位置,去掉多餘的部分(其實就是ROI感興趣的區域,此處不在贅述),如下圖:
步驟五:識別
(略,識別部分很多方式:模版、特徵點、深度學習等等,後續寫到實際開發部分詳解後會與此處進行關聯)。
Demo源碼
void OpenCVManager::testFindContours()
{
QString fileName1 =
"E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/5.jpg";
cv::Mat srcMat = cv::imread(fileName1.toStdString());
cv::Mat dstMat;
int width = 300;
int height = 200;
cv::resize(srcMat, srcMat, cv::Size(width, height));
cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName);
cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2,
srcMat.rows * 5),
srcMat.type());
int sigmaS = 100;
int sigmaR = 1.0;
int thresh = 215;
int maxval = 255;
while(true)
{
// 刷新全圖黑色
windowMat = cv::Scalar(0, 0, 0);
// 原圖複製
cv::Mat mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);
cv::Mat tempMat;
{
{
cvui::printf(windowMat, 75 + width * 1, 40 + height * 0, "sigmaS");
cvui::trackbar(windowMat, 75 + width * 1, 50 + height * 0, 165, &sigmaS, 101, 10000);
cvui::printf(windowMat, 75 + width * 1, 90 + height * 0, "sigmaR");
cvui::trackbar(windowMat, 75 + width * 1, 100, 165 + height * 0, &sigmaR, 1, 100);
// 使用自適應流形應用高維濾波。
cv::Ptr<cv::ximgproc::AdaptiveManifoldFilter> pAdaptiveManifoldFilter
= cv::ximgproc::createAMFilter(sigmaS/100.0f, sigmaR/100.0f, true);
pAdaptiveManifoldFilter->filter(srcMat, tempMat);
// 效果圖copy
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, tempMat, 1.0f, 0.0f, mat);
}
// 轉爲灰度圖像
cv::cvtColor(tempMat, tempMat, cv::COLOR_BGR2GRAY);
{
// 調整閾值化的參數thresh
cvui::printf(windowMat, 75 + width * 1, 20 + height * 1, "thresh");
cvui::trackbar(windowMat, 75 + width * 1, 40 + height * 1, 165, &thresh, 0, 255);
// 調整閾值化的參數maxval
cvui::printf(windowMat, 75 + width * 1, 80 + height * 1, "maxval");
cvui::trackbar(windowMat, 75 + width * 1, 100 + height * 1, 165, &maxval, 0, 255);
// 閾值化
cv::threshold(tempMat, tempMat, thresh, maxval, cv::THRESH_BINARY);
// 效果圖copy
mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
// 轉還圖像
cv::Mat grayMat;
cv::cvtColor(tempMat, grayMat, cv::COLOR_GRAY2BGR);
cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
}
// 尋找輪廓
{
qDebug() << __FILE__ << __LINE__;
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
// 查找輪廓
cv::findContours(tempMat, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
// 遍歷所有頂層輪廓,並繪製出來
dstMat = srcMat.clone();
cv::Mat emptyMat = srcMat.clone();
emptyMat = cv::Scalar(0,0,0);
qDebug() << __FILE__ << __LINE__;
// 輪廓contours[i]對應4個hierarchy元素hierarchy[i][0]~ hierarchy[i][3],
// hierarchy[i][0]表示後一個輪廓的索引編號
// hierarchy[i][1]前一個輪廓的索引編號
// hierarchy[i][2]父輪廓的索引編號
// hierarchy[i][3]內嵌輪廓的索引編號
for(int index = 0; index >=0; index = hierarchy[index][0])
{
if(hierarchy.size() <= 0)
{
break;
}
cv::Scalar color;
if(index < hierarchy.size() / 3)
{
color = cv::Scalar(250 / (hierarchy.size() / 3) * index, 255, 255);
}else if(index < hierarchy.size() / 3 * 2)
{
color = cv::Scalar(255, 250 / (hierarchy.size() / 3) * (index - hierarchy.size() / 3), 255);
}else
{
color = cv::Scalar(255, 255, 250 /
(hierarchy.size() / 3 == 0? 1 : hierarchy.size() / 3) * (index - hierarchy.size() / 3 * 2));
}
// 繪製輪廓裏面的第幾個
cv::drawContours(dstMat, contours, index, color, CV_FILLED, 8, hierarchy);
cv::drawContours(emptyMat, contours, index, color, CV_FILLED, 8, hierarchy);
qDebug() << __FILE__ << __LINE__ << "index =" << index << "total =" << hierarchy.size();
}
// 效果圖copy
mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, emptyMat, 1.0f, 0.0f, mat);
// 效果圖copy
mat = windowMat(cv::Range(srcMat.rows * 4, srcMat.rows * 5),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
}
}
// 更新
cvui::update();
// 顯示
cv::imshow(windowName, windowMat);
// esc鍵退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
工程模板:對應版本號v1.44.0
對應版本號v1.44.0
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105843092