1.霍夫变换直线检测和圆检测

霍夫变换是一种从空间域到极坐标域的转换。已知二维空间的一条直线有很多表现方式,例如截距式斜率式比如:y=kx+b,一旦我们知道k和b,就知道了这条直线,而k和b在坐标上表示的只是一个点而已,如果直线的k相同也就是直线平行,那么表现在kb坐标域也就是条k直线。那么问题在于,如何在kb坐标系表示一个点呢?显然不是很好表示,因为过一个点直线的kb不同的话,kb值表示了一整个坐标系。如果点表示出来是一条直线或者曲线是不是就很好了呢?

霍夫变换和这种想法类似,归根到底是寻找直线的不同表现方法,霍夫变换使用原点到直线的距离p和对一个角度θ表示方程为:
opencv 旋转角度检测 java_霍夫变换
而过一个点的直线,在相应极坐标域中表示为一条正弦曲线,正弦曲线相交两点就共线,这样就达到了一开始的目的,找到了一条直线。

变换到极坐标中,从[0~360]空间,可以得到r的大小

属于同一条直线上点在极坐标空(r, theta)必然在一个点上有最强的信号出现,根据此反算到平面坐标中就可以得到直线上各点的像素坐标。从而得到直线

同样根据类似原理可以进行任意形状的检测,归根到底就是找出形状的点的相同之处,例如使用一个关于角度的函数和一个关于距离的函数表达任意形状,根据已知形状构建直角空间到这两个函数之间的映射关系,在将一幅图中的所有点都去进行该变换,查表如果点都集中在一个地方说明满足形状这一特征。可以总结为变换(找特征)+阈值(分类)问题。

圆检测的源码原理

API:

直线检测:

cv::HoughLinesP(

InputArray src, // 输入图像,必须8-bit的灰度图像
OutputArray lines, // 输出的极坐标来表示直线
double rho, // 生成极坐标时候的像素扫描步长 一般取1
double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180
int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线
double minLineLength=0;// 最小直线长度
double maxLineGap=0;// 最大间隔
)
返回vec2 rho 和theta
cv::HoughLines(
InputArray src, // 输入图像,必须8-bit的灰度图像
OutputArray lines, // 输出的极坐标来表示直线
double rho, // 生成极坐标时候的像素扫描步长
double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180
int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线
double srn=0;// 是否应用多尺度的霍夫变换,如果不是设置0表示经典霍夫变换
double stn=0;//是否应用多尺度的霍夫变换,如果不是设置0表示经典霍夫变换
double min_theta=0; // 表示角度扫描范围 0 ~180之间, 默认即可
double max_theta=CV_PI
)
返回vec4 两点坐标
圆检测:
HoughCircles(
InputArray image, // 输入图像 ,必须是8位的单通道灰度图像
OutputArray circles, // 输出结果,发现的圆信息
Int method, // 方法 - HOUGH_GRADIENT
Double dp, // dp = 1;
Double mindist, // 10 最短距离-可以分辨是两个圆的,否则认为是同心圆- src_gray.rows*/8*
Double param1, // canny edge detection low threshold
Double param2, // 中心点累加器阈值 – 候选圆心
Int minradius, // 最小半径
Int maxradius//最大半径
)

返回vec3 圆心和半径

代码案例:

#include"pch.h"
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;


Mat src, gray_src, dst_line, dst_circle;


int main(int argc, char** argv) {
	src = imread("test.png");
	if (!src.data) {
		printf("没找到图像");
		return-1;
	}
	Canny(src, gray_src, 150, 200);
	cvtColor(gray_src, dst_line, CV_GRAY2BGR);
	dst_line.copyTo(dst_circle);


	namedWindow("input", CV_WINDOW_AUTOSIZE);
	imshow("input", gray_src);

	vector<Vec2f>lines;//选择容器类型

	//API缺陷在于返回的是ρ和θ,代表一条直线,并不是返回这些点。因此画出来的直线之能是全屏直线,无法是某段线段。

	HoughLines(gray_src, lines, 1, CV_PI / 180, 70, 0, 0);//返回类型是含有θ和ρ的矢量lines


	for (size_t i = 1; i < lines.size(); i++) {

		float rho = lines[i][0]; 
		float 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));
		Scalar color= Scalar(0, 0, 255);
		line(dst_line, pt1, pt2, color, 1, CV_AA);//通过1000控制两个点之间的间隔,连线
		
	}

	vector<Vec4f>plines;
	HoughLinesP(gray_src, plines, 1, CV_PI / 180, 30, 0, 0);//返回空间域坐标,也就是点的坐标因此出来的结果是线段
	for (size_t i = 1; i < plines.size(); i++) {
		Vec4f hline = plines[i];
		line(dst_line, Point(hline[0], hline[1]), Point(hline[2], hline[3]), Scalar(255,0,255), 3, LINE_AA);
	}
	namedWindow("output", CV_WINDOW_AUTOSIZE);
	imshow("output", dst_line);

	vector<Vec3f>cir;
	HoughCircles(gray_src, cir, HOUGH_GRADIENT, 1,10,40,40,100);
	for (size_t i = 1; i < cir.size(); i++) {
		Vec3f circlepoint = cir[i];
		circle(dst_circle, Point(circlepoint[0], circlepoint[1]), circlepoint[2], Scalar(0, 255, 0), 1);
	}
	namedWindow("output2", CV_WINDOW_AUTOSIZE);
	imshow("output2", dst_circle);

	waitKey(0);
	return 0;

}

结果:

opencv 旋转角度检测 java_opencv_02