opencv的實用研究--分析輪廓並尋找邊界點
輪廓是圖像處理中非常常見的。對現實中的圖像進行采樣、色彩變化、灰度變化之后,能夠處理得到的是“輪廓”。它直接地反應你了需要分析對象的邊界特征。而對輪廓的分析,實際上也就是對原圖像特征的分析。
在Opencv中,已經實現了基礎的輪廓算法,但是相比較於比如halcon這樣的專業軟件,在輪廓處理這塊的功能還是比較缺乏的。這里就通過一個具體問題,說明自己的學習研究。不對之處歡迎批評。
P.S這里的輪廓處理相關函數,已經包涵在GOBase中,具體可以到公告中找Github.
一、問題提出
那么如果對於一個簡單的圖像,比如
已經獲得了最大物體的輪廓,比如
//灰度域變化
threshold(gray,gray,
0,
255,THRESH_BINARY_INV);
GaussianBlur(gray,gray,Size(
3,
3),
0,
0);
//尋找和繪制輪廓
VP bigestContour
= FindBigestContour(gray);
contours.push_back(bigestContour);
由於在opencv里面,輪廓是以
保存的,那
么如何獲得這個輪廓的四個頂點了?
-
vector <vector <point >>
嘗試直接打印輪廓中第一個點,那么的確是左上角
但是不具有通用性,在一些比較復雜的圖片上面效果不行,比如
那么也就是說,必須通過特征分析的方法獲得已經獲得的輪廓中點的特性,而opencv本身沒有提供相關功能。
二、直觀的解決
現在,對於“左上”和“右下”的兩個點,是比較好分析的。因為在所有的包含在輪廓中的點中,他們一個是x,y同時最小的,一個是x,y同時最大的。
比較復雜的是“左下”和"右上"兩個點,
因為他們的數值不是非常有特征,
比較容易產生混淆。這個時候,如果僅僅是通過x,y值來分析,即使是對於簡單圖像,也很難得到穩定的結果。
-
int itopleft = 65535;
int idownright = 0;
Point ptopleft;
Point pdownright;
Point pdownleft;
for( int i = 0;i <bigestContour.size();i ++){
//左上
if(bigestContour[i].x + bigestContour[i].y <itopleft){
itopleft = bigestContour[i].x + bigestContour[i].y ;
ptopleft = bigestContour[i];
}
//右下
if(bigestContour[i].x +bigestContour[i].y >idownright){
idownright = bigestContour[i].x +bigestContour[i].y;
pdownright = bigestContour[i];
}
}
int idownleft = 65534;
//對於左下的點來說,應該是所有y大於左上的點中,x最小的
for( int i = 0;i <bigestContour.size();i ++){
if(bigestContour[i].y >ptopleft.y){
if(bigestContour[i].x <idownleft){
idownleft = bigestContour[i].x;
pdownleft = bigestContour[i];
}
}
}
//繪制
circle(board,ptopleft, 10,Scalar( 255), 5);
circle(board,pdownright, 10,Scalar( 255), 5);
circle(board,pdownleft, 10,Scalar( 255), 5);
三、利用模型來解決
那么,直觀的方法是不穩定的。這個時候,我想到在進行圖像處理的時候,有所謂“特征點”的說法。比較常見的比如harris
/shift/surf。那么我是否能夠通過分析輪廓圖像,找到輪廓圖像特征點的方法找到我需要的邊角了?
編碼實現:
///在board上尋找角點
///// Detector parameters
int blockSize
=
2;
int apertureSize
=
3;
double k
=
0.
04;
int thresh
=
1;
/// Detecting corners
board.convertTo(board,CV_32F);
cornerHarris( board,dst,
2,
3,
0.
04);
///// Normalizing
normalize( dst, dst_norm,
0,
255, NORM_MINMAX, CV_32FC1, Mat() );
convertScaleAbs( dst_norm, dst_norm_scaled );
///// Drawing a circle around corners
for(
int j
=
0; j
< dst_norm.rows ; j
++ ) {
for(
int i
=
0; i
< dst_norm.cols; i
++ ) {
if( (
int) dst_norm.at
<
float
>(j,i)
> thresh ) {
circle( dst_norm_scaled, Point( i, j ),
5, Scalar(
0),
2,
8,
0 );
circle(src,Point( i, j ),
5, Scalar(
255,
0,
0),
-
1,
8,
0 );
}
}
得到結果
NICE,在圖像中已經明顯的顯示出來了4個邊界點,再加上已經有的兩個點,得到結果不成問題。
四、問題進一步研究
但是這里其實是用了一個“投機取巧”的方法,那就是使用圖像處理的才使用的harris算法來分析輪廓。opencv默認實現的harris速度慢且會內存移除。用在這個簡單的例子里面看似可以,但是無法處理現實問題。所以就必須分析原理。
做圖像處理有一段時間了,我經常反思回憶,在圖像處理中,能夠穩定解決問題的,往往依靠的是“先驗知識,本質特征”;越是分析逼近圖像的本質特征,越能夠發現穩定的解決方法。比如對於輪廓的角來說,很容易想到處於邊角的點和兩邊的點肯定具有一定的關系,而這種關系具有特征性。
所以有目的地尋找論文,很快就有了成果:
對於我的研究來說,這篇論文兩個貢獻:一個是告知
首先要對圖像進行高斯模糊,這個是我之前沒有想到的。特別是對於現實世界中的輪廓,這種方法效果很好。因為邊角經過模糊,那么還是邊角,但毛刺經過模糊,能夠有效去除。
論文中的算法實現是比較簡單的,並且給出了簡化算法,直接編碼驗證:
-
//遍歷輪廓,求出所有支撐角度
int icount = bigestContour.size();
float fmax = - 1; //用於保存局部最大值
int imax = - 1;
bool bstart = false;
for ( int i = 0;i <bigestContour.size();i ++){
Point2f pa = (Point2f)bigestContour[(i +icount - 7) %icount];
Point2f pb = (Point2f)bigestContour[(i +icount + 7) %icount];
Point2f pc = (Point2f)bigestContour[i];
//兩支撐點距離
float fa = getDistance(pa,pb);
float fb = getDistance(pa,pc) +getDistance(pb,pc);
float fang = fa /fb;
float fsharp = 1 -fang;
if (fsharp > 0. 05){
bstart = true;
if (fsharp >fmax){
fmax = fsharp;
imax = i;
}
} else{
if (bstart){
circle(board,bigestContour[imax], 10,Scalar( 255), 1);
circle(src,bigestContour[imax], 10,Scalar( 255, 255, 255), 1);
imax = - 1;
fmax = - 1;
bstart = false;
}
}
}
編碼過程中,相比較於原文,有兩處優化(原文中應該也提到了,但是沒有明說):一是通過取模,使得所有的輪廓點都參與運算;二是通過比較,取出角點的局部最大值。
實現效果,比較理想:
五、小結反思
1、掌握知識,如果不能歸納數學模型,並且編碼實現,不叫真正掌握;
2、分析研究,如果從簡單的情況開始,控制變量,往往能夠左右逢源。
您好,測試的圖片是這樣的,需要把圖片里的2段圓弧與2條直線分割出來,
《基於輪廓尖銳度的圖像角點檢測算法》所實現的算法,不清楚能不能實現
這個結果進行處理的話,根據“支撐點”的基本定義,應該只能夠得到這樣的結果:
如果后面關於圓弧和直線的區分,可能要做斜率研究,較為適宜。
附上全部代碼:
#include <opencv2\highgui.hpp>
#include <opencv2\opencv.hpp>
#include "GOCVHelper.h"
#include <iostream>
using namespace std;
using namespace cv;
int main() {
Mat src = imread("e:/sandbox/測試圖片.jpg", IMREAD_COLOR);
Mat gray;
Mat board(src.size(), src.type(), Scalar::all(0));
vector<VP> contours;
//灰度域變化
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, gray, 100, 255, THRESH_OTSU);
bitwise_not(gray, gray);
GaussianBlur(gray, gray, Size(3, 3), 0, 0);
//尋找和繪制輪廓
VP bigestContour = FindBigestContour(gray);
contours.push_back(bigestContour);
//遍歷輪廓,求出所有支撐角度
int icount = bigestContour.size();
float fmax = -1;//用於保存局部最大值
int imax = -1;
bool bstart = false;
for (int i = 0; i < bigestContour.size(); i++) {
Point2f pa = (Point2f)bigestContour[(i + icount - 7) % icount];
Point2f pb = (Point2f)bigestContour[(i + icount + 7) % icount];
Point2f pc = (Point2f)bigestContour[i];
//兩支撐點距離
float fa = getDistance(pa, pb);
float fb = getDistance(pa, pc) + getDistance(pb, pc);
float fang = fa / fb;
float fsharp = 1 - fang;
if (fsharp > 0.05) {
bstart = true;
if (fsharp > fmax) {
fmax = fsharp;
imax = i;
}
}
else {
if (bstart) {
circle(board, bigestContour[imax], 10, Scalar(255), 1);
circle(src, bigestContour[imax], 10, Scalar(0, 0, 0), 1);
imax = -1;
fmax = -1;
bstart = false;
}
}
}
imshow("src", src);
waitKey(0);
}















