学习OpenCV范例(十五)——霍夫变换

本次范例通过霍夫变换检测直线和圆,讲解霍夫线变换和霍夫圆变换的原理,代码实现,和演示结果,使用霍夫线变换之前, 首先要对图像进行边缘检测的处理,也即霍夫线变换的直接输入只能是边缘二值图像。而霍夫圆变换则只要输入灰度图像即可,因为在霍夫圆变换的过程中已经用到了canny边缘检测。

1、原理

霍夫线变换:

  1. 众所周知, 一条直线在图像二维空间可由两个变量表示. 例如:

    1. 在 笛卡尔坐标系: 可由参数:  斜率和截距表示.
    2. 在 极坐标系: 可由参数:  极径和极角表示

    对于霍夫变换, 我们将用 极坐标系 来表示直线. 因此, 直线的表达式可为:

    化简得: 

  2. 一般来说对于点 , 我们可以将通过这个点的一族直线统一定义为:

    这就意味着每一对  代表一条通过点  的直线.

  3. 如果对于一个给定点  我们在极坐标对极径极角平面绘出所有通过它的直线, 将得到一条正弦曲线. 例如, 对于给定点  and 我们可以绘出下图 (在平面  - ):

    只绘出满足下列条件的点  and .

  4. 我们可以对图像中所有的点进行上述操作. 如果两个不同点进行上述操作后得到的曲线在平面  -  相交, 这就意味着它们通过同一条直线. 例如, 接上面的例子我们继续对点: ,  和点 ,  绘图, 得到下图:

    这三条曲线在  -  平面相交于点 , 坐标表示的是参数对 () 或者是说点 , 点  和点  组成的平面内的的直线.

  5. 那么以上的材料要说明什么呢? 这意味着一般来说, 一条直线能够通过在平面  -  寻找交于一点的曲线数量来 检测. 越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成. 一般来说我们可以通过设置直线上点的 阈值 来定义多少条曲线交于一点我们才认为 检测 到了一条直线.

  6. 这就是霍夫线变换要做的. 它追踪图像中每个点对应曲线间的交点. 如果交于一点的曲线的数量超过了 阈值, 那么可以认为这个交点所代表的参数对  在原图像中为一条直线. 

  标准霍夫线变换和统计概率霍夫线变换
  OpenCV实现了以下两种霍夫线变换:
  1. 标准霍夫线变换
  • 原理在上面的部分已经说明了. 它能给我们提供一组参数对  的集合来表示检测到的直线
  • 在OpenCV 中通过函数 HoughLines 来实现
  1. 统计概率霍夫线变换
  • 这是执行起来效率更高的霍夫线变换. 它输出检测到的直线的端点 
  • 在OpenCV 中它通过函数 HoughLinesP 来实现

霍夫圆变换:

霍夫圆变换可以根据霍夫线变换来实现

通过极坐标来表示圆(a,b)表示圆心,R表示半径,则圆表示为:

x = a + Rcosθ

y = b + Rsinθ

θ的值为0-360

一开始我们假设R是已知的,那么我们就可以把x,y空间的公式变换为关于a、b空间的公式:

a = x1 – Rcosθ

b = y1 – Rsinθ

x1、y1为x、y空间中的每一个非零像素点,如此一来,后面的变换就可以跟霍夫线变换一样操作了。

具体操作步骤为:

①、读取一副图像

②、检测边缘,产生一副二值图像

③、对于每个非零像素转换到ab空间中

④、对于每个ab空间中的点,进行累加
⑤、提取数量最多的点作为圆点
在上面提到R是假设已知的,那么当R是未知的情况怎么办?其实很简单,就是将R设置为1、2、3……这样子已知下去,慢慢试,再对R和圆心数量进行阈值就可以了。

2、代码实现

#include "stdafx.h"

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>

using namespace cv;
using namespace std;

Mat src, dst, cdst;
const char* filename = "hough.png";
const char *winname="hough";
const char *trackbarname="1.houghlines\n2.houghlinep\n3.houghcircle";
int savevalue=50,houghtype;
const int maxthreshold=150;

void help()
{
	cout << "\nThis program demonstrates line finding with the Hough transform.\n"
		"Usage:\n"
		"./houghlines <image_name>, Default is pic1.jpg\n" << endl;
}
void choisehoughlines()
{
 
	vector<Vec2f> lines;
	Canny(src, dst,50, 200, 3);
	cvtColor(dst, cdst, CV_GRAY2BGR);
	HoughLines(dst, lines, 1, CV_PI/180, savevalue+10, 0, 0 );

	for( size_t i = 0; i < lines.size(); i++ )
	{
		float rho = lines[i][0], theta = lines[i][1];
		Point pt1, pt2;
		double a = cos(theta), b = sin(theta);
		double x0 = a*rho, y0 = b*rho;
		pt1.x = cvRound(x0 + 1000*(-b));
		pt1.y = cvRound(y0 + 1000*(a));
		pt2.x = cvRound(x0 - 1000*(-b));
		pt2.y = cvRound(y0 - 1000*(a));
		line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
	}
	imshow(winname, cdst); 
}
void choisehoughlinep()
{
	vector<Vec4i> lines;
	Canny(src, dst,50, 200, 3);
	cvtColor(dst, cdst, CV_GRAY2BGR);
	HoughLinesP(dst, lines, 1, CV_PI/180, savevalue+10, savevalue+10, 10 );
	for( size_t i = 0; i < lines.size(); i++ )
	{
		Vec4i l = lines[i];
		line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);
	}
	imshow(winname, cdst);

}
void choisehoughcircle()
{
	vector<Vec3f> circles;
	cvtColor(src, cdst, CV_GRAY2BGR);
	/// Apply the Hough Transform to find the circles
	HoughCircles( src, circles, CV_HOUGH_GRADIENT, 1, dst.rows/10, 200, savevalue+10, 0, 0 );
	/// Draw the circles detected
	printf("%d",circles.size());
	for( size_t i = 0; i < circles.size(); i++ )
	{
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int radius = cvRound(circles[i][2]);
		// circle center
		circle( cdst, center, 3, Scalar(0,255,0), -1, 8, 0 );
		// circle outline
		circle( cdst, center, radius, Scalar(0,0,255), 3, 8, 0 );
	}
		imshow(winname, cdst);
}
void choice(int,void *)
{
	switch (houghtype)
	{
	case 0:choisehoughlines();break;
	case 1:choisehoughlinep();break;
	case 2:choisehoughcircle();break;
	}
}
int main(int argc, char** argv)
{

    src = imread(filename, 0);
	if(src.empty())
	{
		help();
		cout << "can not open " << filename << endl;
		return -1;
	}
	namedWindow(winname);
	createTrackbar(trackbarname,winname,&houghtype,3,choice);
	createTrackbar("thresholdvalue",winname,&savevalue,maxthreshold,choice);
	imshow("source", src);
	choice(0,0);
	waitKey();

	return 0;
}
3、运行结果

学习OpenCV范例(十五)——霍夫变换_第1张图片
                         图1、原图
学习OpenCV范例(十五)——霍夫变换_第2张图片
                                图2、houghlines
学习OpenCV范例(十五)——霍夫变换_第3张图片
                              图3、houghlinep
学习OpenCV范例(十五)——霍夫变换_第4张图片
                              图4、houghcircle

4、总结

霍夫线变需要对图片线进行边缘提取,图像二值化处理,而霍夫圆变换则不用,只需输入灰度图像即可;霍夫圆变换不能检测出同心圆的多个圆,只能标记出其中的一个,而且通过阈值来选择哪个圆,大家可以通过上面的滑动条来动态的检验结果。检测到的结果一般都用vector容器来存储。

5、用到的类和函数

HoughLines:

功能:利用 Hough 变换在二值图像中找到直线
结构:
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )

image :输入 8-比特、单通道 (二值) 图像
lines :输出的角度和r长度
rho :与象素相关单位的距离精度
theta :弧度测量的角度精度
threshold :阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.
srn 、stn :多尺度变换,距离精度 rho 的分母,角度精度 theta 的分母。

HoughLinesP:

功能:通过统计概率的霍夫线变换找到线段
结构:
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
image :输入 8-比特、单通道 (二值) 图像
lines :输出线段的两个端点,保存为(x_1, y_1, x_2, y_2)

rho :与象素相关单位的距离精度
theta :弧度测量的角度精度
threshold :阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.
minLineLength :最小的线段长度
maxLineGap :这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的间隔小于maxLineGap时,将其合二为一。


HoughCircles:

功能:利用 Hough 变换在灰度图像中找圆
结构:
void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )

image :输入 8-比特、单通道 (二值) 图像
circles :输出圆心坐标(x、y),和半径r
method : Hough 变换方式,目前只支持CV_HOUGH_GRADIENT
dp :累加器图像的分辨率。如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。dp的值不能比1小。
minDist :让算法能明显区分的两个不同圆之间的最小距离。
param1 :用于Canny的边缘阀值上限,下限被置为上限的一半。
param2 :累加器的阀值。
minRadius :最小圆半径
maxRadius :最大圆半径,默认为最大值 max(image_width, image_height)




你可能感兴趣的:(HoughCircles,霍夫圆变换,HoughLines,HoughLinesP,霍夫线变换)