這篇文章給大家介紹OpenCV圖像處理中怎樣合理選用Side Window Filter輔助矩形框檢測(cè),內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
10多年建站經(jīng)驗(yàn), 網(wǎng)站設(shè)計(jì)制作、網(wǎng)站設(shè)計(jì)客戶的見(jiàn)證與正確選擇。成都創(chuàng)新互聯(lián)公司提供完善的營(yíng)銷(xiāo)型網(wǎng)頁(yè)建站明細(xì)報(bào)價(jià)表。后期開(kāi)發(fā)更加便捷高效,我們致力于追求更美、更快、更規(guī)范。
今天要干什么?在一張圖片上通過(guò)傳統(tǒng)算法來(lái)檢測(cè)矩形。為了防止你無(wú)聊,先上一組對(duì)比圖片。
這個(gè)算法出自https://stackoverflow.com/questions/8667818/opencv-c-obj-c-detecting-a-sheet-of-paper-square-detection
,接下來(lái)我們就從源碼角度來(lái)理解一下吧。
findContours
算法尋找輪廓區(qū)域。approxPolyDP
算法來(lái)近似輪廓為多邊形。4
,是否為凸多邊形,且相鄰邊的夾角的
cosin
值是否接近0(也即是角度為90度),如果均滿足代表這個(gè)多邊形為矩形,存入結(jié)果中。下面給出上面算法的核心代碼實(shí)現(xiàn)。
const double eps = 1e-7;
//獲取pt0->pt1向量和pt0->pt2向量之間的夾角
static double angle(Point pt1, Point pt2, Point pt0)
{
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2) / sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + eps);
}
//尋找矩形
static void findSquares(const Mat& image, vector >& squares, int N=5, int thresh=50)
{
//濾波可以提升邊緣檢測(cè)的性能
Mat timg(image);
// 普通中值濾波
medianBlur(image, timg, 9);
// SideWindowFilter的中值濾波
// timg = MedianSideWindowFilter(image, 4);
Mat gray0(timg.size(), CV_8U), gray;
// 存儲(chǔ)輪廓
vector > contours;
// 在圖像的每一個(gè)顏色通道尋找矩形
for (int c = 0; c < 3; c++)
{
int ch[] = { c, 0 };
// 函數(shù)功能:mixChannels主要就是把輸入的矩陣(或矩陣數(shù)組)的某些通道拆分復(fù)制給對(duì)應(yīng)的輸出矩陣(或矩陣數(shù)組)的某些通道中,其中的對(duì)應(yīng)關(guān)系就由fromTo參數(shù)制定.
// 接口:void mixChannels (const Mat* src , int nsrc , Mat* dst , int ndst , const int* fromTo , size_t npairs );
// src: 輸入矩陣,可以為一個(gè)也可以為多個(gè),但是矩陣必須有相同的大小和深度.
// nsrc: 輸入矩陣的個(gè)數(shù).
// dst: 輸出矩陣,可以為一個(gè)也可以為多個(gè),但是所有的矩陣必須事先分配空間(如用create),大小和深度須與輸入矩陣等同.
// ndst: 輸出矩陣的個(gè)數(shù)
// fromTo:設(shè)置輸入矩陣的通道對(duì)應(yīng)輸出矩陣的通道,規(guī)則如下:首先用數(shù)字標(biāo)記輸入矩陣的各個(gè)通道。輸入矩陣個(gè)數(shù)可能多于一個(gè)并且每個(gè)矩陣的通道可能不一樣,
// 第一個(gè)輸入矩陣的通道標(biāo)記范圍為:0 ~src[0].channels() - 1,第二個(gè)輸入矩陣的通道標(biāo)記范圍為:src[0].channels() ~src[0].channels() + src[1].channels() - 1,
// 以此類(lèi)推;其次輸出矩陣也用同樣的規(guī)則標(biāo)記,第一個(gè)輸出矩陣的通道標(biāo)記范圍為:0 ~dst[0].channels() - 1,第二個(gè)輸入矩陣的通道標(biāo)記范圍為:dst[0].channels()
// ~dst[0].channels() + dst[1].channels() - 1, 以此類(lèi)推;最后,數(shù)組fromTo的第一個(gè)元素即fromTo[0]應(yīng)該填入輸入矩陣的某個(gè)通道標(biāo)記,而fromTo的第二個(gè)元素即
// fromTo[1]應(yīng)該填入輸出矩陣的某個(gè)通道標(biāo)記,這樣函數(shù)就會(huì)把輸入矩陣的fromTo[0]通道里面的數(shù)據(jù)復(fù)制給輸出矩陣的fromTo[1]通道。fromTo后面的元素也是這個(gè)
// 道理,總之就是一個(gè)輸入矩陣的通道標(biāo)記后面必須跟著個(gè)輸出矩陣的通道標(biāo)記.
// npairs: 即參數(shù)fromTo中的有幾組輸入輸出通道關(guān)系,其實(shí)就是參數(shù)fromTo的數(shù)組元素個(gè)數(shù)除以2.
mixChannels(&timg, 1, &gray0, 1, ch, 1);
// 嘗試幾個(gè)不同的閾值
for (int l = 0; l < N; l++)
{
// hack: use Canny instead of zero threshold level.
// Canny helps to catch squares with gradient shading
// 在級(jí)別為0的時(shí)候不使用閾值為0,而是使用Canny邊緣檢測(cè)算子
if (l == 0)
{
// void Canny( InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false);
// 第一個(gè)參數(shù):輸入圖像(八位的圖像)
// 第二個(gè)參數(shù):輸出的邊緣圖像
// 第三個(gè)參數(shù):下限閾值,如果像素梯度低于下限閾值,則將像素不被認(rèn)為邊緣
// 第四個(gè)參數(shù):上限閾值,如果像素梯度高于上限閾值,則將像素被認(rèn)為是邊緣(建議上限是下限的2倍或者3倍)
// 第五個(gè)參數(shù):為Sobel()運(yùn)算提供內(nèi)核大小,默認(rèn)值為3
// 第六個(gè)參數(shù):計(jì)算圖像梯度幅值的標(biāo)志,默認(rèn)值為false
Canny(gray0, gray, 5, thresh, 5);
// 執(zhí)行形態(tài)學(xué)膨脹操作
dilate(gray, gray, Mat(), Point(-1, -1));
}
else
{
// 當(dāng)l不等于0的時(shí)候,執(zhí)行 tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
gray = gray0 >= (l + 1) * 255 / N;
}
// 尋找輪廓并將它們?nèi)看鎯?chǔ)為列表
findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
//存儲(chǔ)一個(gè)多邊形(矩形)
vector approx;
// 測(cè)試每一個(gè)輪廓
for (size_t i = 0; i < contours.size(); i++)
{
// 近似輪廓,精度與輪廓周長(zhǎng)成正比,主要功能是把一個(gè)連續(xù)光滑曲線折線化,對(duì)圖像輪廓點(diǎn)進(jìn)行多邊形擬合。
// 函數(shù)聲明:void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
// InputArray curve:一般是由圖像的輪廓點(diǎn)組成的點(diǎn)集
// OutputArray approxCurve:表示輸出的多邊形點(diǎn)集
// double epsilon:主要表示輸出的精度,就是兩個(gè)輪廓點(diǎn)之間最大距離數(shù),5,6,7,,8,,,,,
// bool closed:表示輸出的多邊形是否封閉
// arcLength 計(jì)算圖像輪廓的周長(zhǎng)
approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);
// 近似后,方形輪廓應(yīng)具有4個(gè)頂點(diǎn)
// 相對(duì)較大的區(qū)域(以濾除嘈雜的輪廓)并且是凸集。
// 注意: 使用面積的絕對(duì)值,因?yàn)槊娣e可以是正值或負(fù)值-根據(jù)輪廓方向
if (approx.size() == 4 &&
fabs(contourArea(Mat(approx))) > 1000 &&
isContourConvex(Mat(approx)))
{
double maxCosine = 0;
for (int j = 2; j < 5; j++)
{
// 找到相鄰邊之間的角度的最大余弦
double cosine = fabs(angle(approx[j % 4], approx[j - 2], approx[j - 1]));
maxCosine = MAX(maxCosine, cosine);
}
// 如果所有角度的余弦都很小(所有角度均為90度),將頂點(diǎn)集合寫(xiě)入結(jié)果vector
if (maxCosine < 0.3)
squares.push_back(approx);
}
}
}
}
}
//在圖像上畫(huà)出方形
void drawSquares(Mat &image, const vector >& squares) {
for (size_t i = 0; i < squares.size(); i++)
{
const Point* p = &squares[i][0];
int n = (int)squares[i].size();
//不檢測(cè)邊界
if (p->x > 3 && p->y > 3)
polylines(image, &p, &n, 1, true, Scalar(0, 255, 0), 3, LINE_AA);
}
}
在上面的代碼中,完全是按照算法原理的步驟來(lái)進(jìn)行實(shí)現(xiàn),比較容易理解。我在測(cè)試某張圖片的時(shí)候發(fā)現(xiàn),如果把Side Window Filter應(yīng)用到這里有時(shí)候會(huì)產(chǎn)生更好的效果,因此實(shí)現(xiàn)了一下用于中值濾波的Side Window Filter,介于篇幅原因請(qǐng)到我的github查看,地址為:https://github.com/BBuf/Image-processing-algorithm/blob/master/MedianSideWindowFilter.cpp
。關(guān)于SideWindowFilter可以看我們前兩天的文章:【AI移動(dòng)端算法優(yōu)化】一,CVRR 2018 Side Window Filtering 論文解讀和C++實(shí)現(xiàn)
這個(gè)例子普通中值濾波就做得比較好了,也就沒(méi)必要用Side Window Filter的中值濾波了。
可以看到在最后這張圖中,因?yàn)槭褂昧薙ide Window Filter,在排除了一些噪聲的同時(shí)保住了邊緣和角點(diǎn),使得檢出率提高了很多,也說(shuō)明了Side Window Filter的有效性。
關(guān)于OpenCV圖像處理中怎樣合理選用Side Window Filter輔助矩形框檢測(cè)就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。