提取直線、輪廓

1. 用 Canny 算子檢測圖像輪廓

介紹:

使用 Canny 算子,需要指定兩個閾值,基於兩個閾值獲得二值分佈圖的策略(滯後閾值化)。在低閾值邊緣分佈圖上值保留具有連續路徑的邊緣點,同時把那些邊緣點連接到屬於高閾值邊緣分佈圖的邊緣上,高閾值的邊緣點被保留下來,低閾值分佈圖上的孤立點全部被移除。


例子代碼

<code>#include<opencv2>

#include<iostream>

#include<string>

using namespace cv;

using namespace std;

void test()

{
\tMat srcImg = imread("road.jpg");
\tif (srcImg.empty())
\t{
\t\tcout << "could not load image...\\n" << endl;
\t\treturn;
\t}

\tnamedWindow("input image", CV_WINDOW_AUTOSIZE);
\timshow("input image", srcImg);

\tMat grayImg,contoursImg;
\tcvtColor(srcImg, grayImg, CV_BGR2GRAY);
\tCanny(grayImg, contoursImg, 125, 350);
\tnamedWindow("output image", CV_WINDOW_AUTOSIZE);
\timshow("output image", contoursImg);
}


int main(
{

\ttest();

\twaitKey(0);

\treturn 0;

}/<string>/<iostream>/<opencv2>/<code>

效果圖


提取直線、輪廓


2. 發現輪廓

介紹:是基於圖像邊緣提取基礎尋找對象輪廓的方法,邊緣提取的閾值選定會影響最終輪廓發現結果。

步驟:

(1)輸入圖像轉為灰度圖像

(2)使用 Canny 進行邊緣提取,得到二值圖像

(3)使用 findContours 尋找輪廓

(4)使用 drawContours 繪製輪廓

例子代碼

<code>#include<opencv2>
#include<iostream>
#include<string>

using namespace cv;
using namespace std;

Mat srcImg, dstImg;
int thresholdValue = 100;
int thresholdMax = 255;

void DemoContours(int ,void *)
{

\tMat cannyOutput;
\tvector<vector>>contours;
\tvector<vec4i> hierachy; //層次
\tCanny(srcImg, cannyOutput, thresholdValue, thresholdValue * 2, 3, false);
\t//找輪廓
\tfindContours(cannyOutput, //輸入圖像,非0的像素被看成1,0的像素值保持不變,8-bit
\t\t\t\t contours, //全部發現的輪廓對象
\t\t\t\t hierachy, //該圖的拓撲結構,可選,該輪廓發現算法正是基於圖像拓撲結構實現
\t\t\t\t RETR_TREE, //輪廓返回模式
\t\t\t\t CHAIN_APPROX_SIMPLE, //發現方法
\t\t\t\t Point(0, 0)); //輪廓像素的位移 默認(0, 0)沒有位移
\tdstImg = Mat::zeros(srcImg.size(), CV_8UC3); //初始化
\tRNG rng(12345);
\t//畫輪廓
\tfor (size_t i = 0; i < contours.size(); i++)
\t{

\t\tScalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
\t\tdrawContours(dstImg, //輸出圖像
rs, //全部發現的輪廓對象

\t\t\ti, //輪廓索引號

\t\t\tcolor, //繪製時的顏色

\t\t\t2,\t\t\t\t //繪製線寬

\t\t\t8, //線的類型 LINE_8

\t\t\thierachy, //拓撲結構圖

\t\t\t0, //最大層數,0只繪製當前,1表示繪製當前及其內嵌的輪廓

\t\t\tPoint(0, 0)); //輪廓位移,可選


\t}

\timshow("output image", dstImg);

}

void test()

{

\tsrcImg = imread("coins.png");

\tif (srcImg.empty())

\t{

\t\tcout << "could not load image...\\n" << endl;

\t\treturn;

\t}

\tnamedWindow("input image", CV_WINDOW_AUTOSIZE);

\tnamedWindow("output image", CV_WINDOW_AUTOSIZE);

\timshow("input image", srcImg);

\tcvtColor(srcImg, dstImg, CV_BGR2GRAY); //轉為灰度圖像

\tcreateTrackbar("Threshold Value", "output image", &thresholdValue, thresholdMax, DemoContours); //加滑動條

\tDemoContours(0, 0);

}

int main()

{

\ttest();

\twaitKey(0);

\treturn 0;

}/<vec4i>/<vector>/<string>/<iostream>/<opencv2>/<code>

效果圖


提取直線、輪廓

3. 用概率霍夫變換檢測直線

例子代碼

<code>#include<opencv2>
#include<iostream>
#include<string>

using namespace cv;
using namespace std;

//用概率霍夫變換檢測直線
#define PI 3.1415926
class LineFinder //創建 LineFinder 類封裝函數的參數
{

public:
\tMat srcImg; //源圖像
\tvector<vec4i> lines; //包含被檢測直線的端點的向量
\tdouble deltaRho, deltaTheta; //累加器的分辨率參數
\tint minVote; //確定直線之前必須收到的最小投票數
\tdouble minLength; //直線的最小長度
\tdouble maxGap; //直線上允許的最大空隙

public:

\t//初始化構造函數,默認累加器分辨率是1像素,1度,沒有空隙,沒有最小長度
\tLineFinder() :deltaRho(1), deltaTheta(PI / 180), minVote(10), minLength(0.), maxGap(0.)
\t{

\t}
\t//設置累加器的分辨率
\tvoid setAccResolution(double dRho, double dTheta)

\t{
\t\tdeltaRho = dRho;
\t\tdeltaTheta = dTheta;
\t}

\t//設置最小投票數
\tvoid setMinVote(int minv)
\t{
\t\tminVote = minv;
\t}

\t//設置直線長度和空隙
\tvoid setLineLengthAndGap(double length, double gap)
\t{
\t\tminLength = length;
\t\tmaxGap = gap;
\t}

\t//用上述方法,檢測霍夫線段
\tvector<vec4i> findlines(Mat &binary)
\t{
\t\tlines.clear();
\t\tHoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);
\t\treturn lines;
\t}

\t//用下面的方法在圖像上繪製檢測到的線段
\tvoid drawDetectedLine(Mat &image, Scalar color = Scalar(255, 255, 255))
\t{
\t\t//畫線
\t\tvector<vec4i> ::const_iterator it2 = lines.begin();
\t\twhile (it2 !=lines.end())
\t\t{
\t\t\tPoint pt1((*it2)[0], (*it2)[1]);
\t\t\tPoint pt2((*it2)[2], (*it2)[3]);
\t\t\tline(image, pt1, pt2, color);
\t\t\t++it2;
\t\t}
\t}
};

void test()
{
\tMat srcImg = imread("road.jpg");
\tif (srcImg.empty())
\t{

\t\tcout << "could not load image...\\n" << endl;
\t\treturn;
\t}

\tnamedWindow("Original image", CV_WINDOW_AUTOSIZE);
\timshow("Original image", srcImg);

\tMat grayImg,contoursImg;
\tcvtColor(srcImg, grayImg, CV_BGR2GRAY); //轉為灰度圖像
\tCanny(grayImg, contoursImg, 125, 350);
\tnamedWindow("Canny Contours", CV_WINDOW_AUTOSIZE);
\timshow("Canny Contours", contoursImg);

\tLineFinder finder; //創建實例
\tfinder.setLineLengthAndGap(100, 20); //設置霍夫變換的參數
\tfinder.setMinVote(60); //設置最小投票數
\tvector<vec4i> lines;
\tlines = finder.findlines(contoursImg); //檢測
\tfinder.drawDetectedLine(srcImg); //畫線
\tnamedWindow("Lines With HoughP", CV_WINDOW_AUTOSIZE);
\timshow("Lines With HoughP", srcImg);
}

int main()
{
\ttest();
\twaitKey(0);
\treturn 0;
}/<vec4i>/<vec4i>/<vec4i>/<vec4i>/<string>/<iostream>/<opencv2>/<code>

效果圖


提取直線、輪廓

4. 點集的直線擬合

光檢測直線還不夠,還要精確的估計直線的位置和方向。首先需要識別出圖像中靠近直線的點,為了提取靠近直線的點集,做法是在黑色圖像上畫一條白線,並且穿過用於檢測直線的 Canny 輪廓圖。

例子代碼

<code>#include<opencv2>
#include<iostream>
#include<string>

using namespace cv;
using namespace std;

//點集的直線擬合
#define PI 3.1415926
class LineFinder //創建 LineFinder 類封裝函數的參數
{
public:

\tMat srcImg; //源圖像
\tvector<vec4i> lines; //包含被檢測直線的端點的向量
\tdouble deltaRho, deltaTheta; //累加器的分辨率參數
\tint minVote; //確定直線之前必須收到的最小投票數
\tdouble minLength; //直線的最小長度
\tdouble maxGap; //直線上允許的最大空隙

public:

\t//初始化構造函數,默認累加器分辨率是1像素,1度,沒有空隙,沒有最小長度

\tLineFinder() :deltaRho(1), deltaTheta(PI / 180), minVote(10), minLength(0.), maxGap(0.)
\t{

\t}

\t//設置累加器的分辨率
\tvoid setAccResolution(double dRho, double dTheta)
\t{
\t\tdeltaRho = dRho;
\t\tdeltaTheta = dTheta;
\t}

\t//設置最小投票數
\tvoid setMinVote(int minv)
\t{
\t\tminVote = minv;
\t}

\t//設置直線長度和空隙
\tvoid setLineLengthAndGap(double length, double gap)
\t{
\t\tminLength = length;
\t\tmaxGap = gap;
\t}

\t//用上述方法,檢測霍夫線段
\tvector<vec4i> findlines(Mat &binary)
\t{
\t\tlines.clear();
\t\tHoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);
\t\treturn lines;
\t}

\t//用下面的方法在圖像上繪製檢測到的線段
\tvoid drawDetectedLine(Mat &image, Scalar color = Scalar(255, 255, 255))
\t{
\t\t//畫線
\t\tvector<vec4i> ::const_iterator it2 = lines.begin();
\t\twhile (it2 != lines.end())
\t\t{
\t\t\tPoint pt1((*it2)[0], (*it2)[1]);
\t\t\tPoint pt2((*it2)[2], (*it2)[3]);
\t\t\tline(image, pt1, pt2, color);
\t\t\t++it2;
\t\t}

\t}
};

void test()
{
\tMat srcImg = imread("road.jpg");
\tif (srcImg.empty())
\t{
\t\tcout << "could not load image...\\n" << endl;
\t\treturn;
\t}

\tnamedWindow("Original image", CV_WINDOW_AUTOSIZE);
\timshow("Original image", srcImg);

\tMat grayImg, contoursImg;
\tcvtColor(srcImg, grayImg, CV_BGR2GRAY); //轉為灰度圖像
\tCanny(grayImg, contoursImg, 125, 350);
\tnamedWindow("Canny Contours", CV_WINDOW_AUTOSIZE);
\timshow("Canny Contours", contoursImg);

\tLineFinder finder; //創建實例
\tfinder.setLineLengthAndGap(100, 20); //設置霍夫變換的參數
\tfinder.setMinVote(60); //設置最小投票數
\tvector<vec4i> lines;
\tlines = finder.findlines(contoursImg); //檢測
\tfinder.drawDetectedLine(srcImg); //畫線
\tnamedWindow("Lines With HoughP", CV_WINDOW_AUTOSIZE);
\timshow("Lines With HoughP", srcImg);
\tvector<vec4i> ::const_iterator it2 = lines.begin();
\twhile (it2 != lines.end())
\t{
\t\tcout << "(" << (*it2)[0] << "," << (*it2)[1] << ")-(" << (*it2)[2] << "," << (*it2)[3] << ")" << endl;
\t\t++it2;
\t}

\t//(1)在黑色圖像上畫一條白線,並且穿過用於檢測直線的 Canny 輪廓圖。結果是包含了與指定直線相關點的圖像
\tint n = 0; //選用直線 0
\tline(srcImg, Point(lines[n][0], lines[n][1]), Point(lines[n][2], lines[n][3]), Scalar(255), 3);
\tnamedWindow("One line of the iamge", CV_WINDOW_AUTOSIZE);
\timshow("One line of the iamge", srcImg);

\tMat oneLine(srcImg.size(), CV_8U, Scalar(0)); //黑色圖像
\tline(oneLine, Point(lines[n][0], lines[n][1]), Point(lines[n][2], lines[n][3]), Scalar(255), 3); //白色直線
\tbitwise_and(contoursImg, oneLine, oneLine); //輪廓與直線進行“與”運算
\tnamedWindow("One line", CV_WINDOW_AUTOSIZE);
\timshow("One line", 255 - oneLine);

\t//(2)然後把這些集合內點的座標插入到 Point 對象的 vector 類型中
\tvector<point> points;
\t//迭代遍歷像素,得到所有點的位置
\tfor (int i = 0; i < oneLine.rows; i++)
\t{
\t\t//行 i
\t\tuchar *rowPtr = oneLine.ptr<uchar>(i);
\t\tfor (int j = 0; j < oneLine.cols; j++)
\t\t{
\t\t\t//列 j
\t\t\tif (rowPtr[j]) //如果在輪廓上
\t\t\t{
\t\t\t\tpoints.push_back(Point(j, i));
\t\t\t}
\t\t}
\t}
\t//(3)得到點集後,利用這些點集擬合出直線
\tVec4f line;
\tfitLine(points, line,
\t\tDIST_L2, //距離類型
\t\t0, //L2類型不用這個參數
\t\t0.01, 0.01); //精度
\tcout << "line:(" << line[0] << "," << line[1] << ")" << line[2] << "," << line[3] << ")\\n";
\tint x0 = line[2]; //直線上一個點
\tint y0 = line[3];
\tint x1 = x0 + 100 * line[0]; //加上長度為 100 的向量
\tint y1 = y0 + 100 * line[1]; //用單位向量生成

\t//繪製這條直線
\tcv::line(srcImg, Point(x0, y0), Point(x1, y1), 0,2);
\tnamedWindow("Fitted line", CV_WINDOW_AUTOSIZE);
\timshow("Fitted line", srcImg);

}

int main()
{
\ttest();
\twaitKey(0);
\treturn 0;
}/<uchar>/<point>/<vec4i>/<vec4i>/<vec4i>/<vec4i>/<vec4i>/<string>/<iostream>/<opencv2>/<code>

效果圖


提取直線、輪廓

獲取例子所用的圖片可以關注微信OpenCV圖像處理算法獲得哦,裡面也會寫我學習圖像處理歷程,歡迎大佬來交流!!!


分享到:


相關文章: