2017-01-20 10:07:54 yangdashi888 阅读数 944

1、霍夫变换可以用于检测直线和圆,其主要是利用了极坐标的方式。

其中直线检测的极坐标表示方法是:

X *cos(theta) + y * sin(theta)  = r 其中角度theta指r与X轴之间的夹角,r为到直线几何垂

直距离。任何在直线上点,x, y都可以表达,其中 r, theta是常量。该公式图形表示如下:

然而在实现的图像处理领域,图像的像素坐标P(x, y)是已知的,而r, theta则是我们要寻找

的变量。如果我们能绘制每个(r, theta)值根据像素点坐标P(x, y)值的话,那么就从图像笛卡

尔坐标系统转换到极坐标霍夫空间系统,这种从点到曲线的变换称为直线的霍夫变换。变换

通过量化霍夫参数空间为有限个值间隔等分或者累加格子。当霍夫变换算法开始,每个像素

坐标点P(x, y)被转换到(r, theta)的曲线点上面,累加到对应的格子数据点,当一个波峰出现

时候,说明有直线存在。同样的原理,我们可以用来检测圆,只是对于圆的参数方程变为如

下等式:

(x –a ) ^2 + (y-b) ^ 2 = r^2其中(a, b)为圆的中心点坐标,r圆的半径。这样霍夫的参数空间就

变成一个三维参数空间。给定圆半径转为二维霍夫参数空间,变换相对简单,也比较常用。


2019-03-09 14:31:21 qq_37855507 阅读数 162

  霍夫变换是一个非常重要的检测间断点边界形状的方法,他通过将图像坐标空间变换到参数空间,来实现直线和曲线的拟合。

一、直线检测:

1.直角坐标参数空间:

在这里插入图片描述
一条直线可以用数学表达式y = mx + c 或者 = x cos θ + y sinθ 表示。ρ是从原点到直线的垂直距离,θ 是直线的垂线与横轴顺时针方向的夹角(如果你使用的坐标系不同方向也可能不同,我是按OpenCV 使用的坐标系描述的)。如下图所示:在这里插入图片描述
所以如果一条线在原点下方经过,θ的值就应该大于0,角度小于180。但是如果从原点上方经过的话,角度不是大于180,也是小于180,但ρ 的值小于0。垂直的线角度为0 度,水平线的角度为90 度。

霍夫变换是如何工作?每一条直线都可以用(ρ; θ) 表示。所以首先创建一个2D 数组(累加器),初始化累加器,所有的值都为0。行表示ρ,列表示θ。这个数组的大小决定了最后结果的准确性。如果你希望角度精确到1 度,你就需要180 列。对于ρ,最大值为图片对角线的距离。所以如果精确度要达到一个像素的级别,行数就应该与图像对角线的距离相等。

想象一下我们有一个大小为100x100 的直线位于图像的中央。取直线上的第一个点,我们知道此处的(x,y)值。把x 和y 带入上边的方程组,然后遍历θ 的取值:0,1,2,3,…,180。分别求出与其对应的 的值,这样我们就得到一系列(ρ; θ) 的数值对,如果这个数值对在累加器中也存在相应的位置,就在这个位置上加1。所以现在累加器中的(50,90)=1。(一个点可能存在与多条直线中,所以对于直线上的每一个点可能是累加器中的多个值同时加1)。
在这里插入图片描述
使用opencv实现上述过程:

#include<opencv2\opencv.hpp>
#include<stdio.h>
#include<iostream>

using namespace cv;
using namespace std;

int main(int arg, char* argv[])
{
	//在如图片并显示;
	Mat img = imread("H:\\MATLAB代码\\霍夫变换的理解\\线条.png");
	//namedWindow("原图");
	//imshow("原图", img);

	destroyAllWindows();
	//将图像灰度化并显示
	Mat grayImage; //创建无初始化矩阵
	cvtColor(img, grayImage, CV_RGB2GRAY);
	//namedWindow("灰度图");
	//imshow("灰度图", grayImage);
	//边缘检测
	Mat edge;
	Canny(grayImage, edge, 3, 9, 3);

	int i, j;

	//行列
	int row = grayImage.rows;
	int col = grayImage.cols;

	//极径最大值为对角线+宽
	//cvCeil 取整,返回不小于参数的整数。
	int max_r = col + cvCeil(sqrt(double(row*row + col*col)));

	//累加器 三角函数
	int *line_cnt[180];  //指针数组,  是一个普通的数组,数组中的每一个元素都是指针。
	double sin_[180], cos_[180], rad_ = CV_PI / 180;
	for (i = 0; i < 180; i++)
	{
		//初始化累加器为0
		line_cnt[i] = new int[max_r]();   //列为180列,列表示theta,行表示r,r最大值为max_r;
		//初始化三角函数
		sin_[i] = sin(i*rad_);
		cos_[i] = cos(i*rad_);
	}

	//极径,极角
	int r = 0;
	int theta = 0;

	//遍历图像,判断并进行累加。
	uchar *p;
	for (i = 0; i < row; i++)
	{
		//用指针遍历图像中的元素。
		//每一行图像的指针;
		p = edge.ptr<uchar>(i);
		for (j = 0; j < col; j++)
		{
			if (p[j] != 0)
			{
				//不等于0,证明是有用的边缘像素点
				//由该点的参数方程 更改累加器
				for (theta = 0; theta < 180; theta++)
				{
					//极坐标 直线方程
					r = cvRound(j*cos_[theta] + i*sin_[theta]);
					//偏移量, 因为累计器(霍夫矩阵的索引没有负值)
					r = r + col;
					//累加器完成累加
					line_cnt[theta][r]++;
				}
			}
		}
	}

	//存放取出最长的n条线
	int n = 6;
	int *line_n[3];            //三行分别用来存储极角、极径、共点数目
	line_n[0] = new int[n]();  //极角
	line_n[1] = new int[n]();  //极径
	line_n[2] = new int[n]();  //共点数目

	int tt = 0, rr = 0, cnt = 0;
	//寻找累加器中最大的值 
	for (theta = 0; theta < 180; theta++)
	{
		for (r = 0; r < max_r; r++)
		{
			//最少共点 < 这条直线的共点 则替换 并尝试进行冒泡
			if (line_n[2][n-1] < line_cnt[theta][r])
			{
				line_n[0][n-1] = theta;
				//累计的时候偏移过,将偏移修改回来
				line_n[1][n-1] = r - col;
				line_n[2][n-1] = line_cnt[theta][r];
				//冒泡排序
				for (i = n - 1; i > 0; i--)
				{
					//如果大于 则交换
					if (line_n[2][i] > line_n[2][i - 1])
					{
						tt = line_n[0][i];
						rr = line_n[1][i];
						cnt = line_n[2][i];
						line_n[0][i] = line_n[0][i - 1];
						line_n[1][i] = line_n[1][i - 1];
						line_n[2][i] = line_n[2][i - 1];
						line_n[0][i - 1] = tt;
						line_n[1][i - 1] = rr;
						line_n[2][i - 1] = cnt;
					}
					else
						break;
				}
			}
		}
	}

	//画出线段
	for (i = 0; i < n; i++)
	{
		Point pt1, pt2;
		double a = cos_[line_n[0][i]], b = sin_[line_n[0][i]];
		double x0 = a*line_n[1][i], y0 = b*line_n[1][i];
		//用点画出检测到的直线。
		//下面四个式子为纯直线几何推导。
		pt1.x = cvRound(x0 + max_r*(-b));
		pt1.y = cvRound(y0 + max_r*(a));
		pt2.x = cvRound(x0 - max_r*(-b));
		pt2.y = cvRound(y0 - max_r*(a));

		//绿线
		line(img, pt1, pt2, Scalar(0, 255, 0), 1, CV_AA);
	}
	cvNamedWindow("直线检测图");
	imshow("直线检测图",img);
	waitKey(0);
	//----------------------释放内存-----------------
	for (i = 0; i < 180; i++)
	{
		delete[]line_cnt[i];
	}
	delete[]line_n[0];
	delete[]line_n[1];
	delete[]line_n[2];
	return 0;
}
2018-09-11 11:00:34 qq_18234121 阅读数 414

原理介绍

霍夫变换在检测各种形状的的技术中非常流行,如果你要检测的形状可以用数学表达式写出,你就可以是使用霍夫变换检测它。及时要检测的形状存在一点破坏或者扭曲也可以使用。我们下面就看看如何使用霍夫变换检测直线。

一条直线可以用数学表达式y = mx + c 或者 = x cos θ + y sinθ 表示。ρ是从原点到直线的垂直距离,θ 是直线的垂线与横轴顺时针方向的夹角(如果你使用的坐标系不同方向也可能不同,我是按OpenCV 使用的坐标系描述的)。如下图所示:

                                                                         

所以如果一条线在原点下方经过,θ的值就应该大于0,角度小于180。但是如果从原点上方经过的话,角度不是大于180,也是小于180,但ρ 的值小于0。垂直的线角度为0 度,水平线的角度为90 度。

       让我们来看看霍夫变换是如何工作的。每一条直线都可以用(ρ; θ) 表示。所以首先创建一个2D 数组(累加器),初始化累加器,所有的值都为0。行表示ρ,列表示θ。这个数组的大小决定了最后结果的准确性。如果你希望角度精确到1 度,你就需要180 列。对于ρ,最大值为图片对角线的距离。所以如果精确度要达到一个像素的级别,行数就应该与图像对角线的距离相等。

        想象一下我们有一个大小为100x100 的直线位于图像的中央。取直线上的第一个点,我们知道此处的(x,y)值。把x 和y 带入上边的方程组,然后遍历θ 的取值:0,1,2,3,...,180。分别求出与其对应的 的值,这样我们就得到一系列(ρ; θ) 的数值对,如果这个数值对在累加器中也存在相应的位置,就在这个位置上加1。所以现在累加器中的(50,90)=1。(一个点可能存在与多条直线中,所以对于直线上的每一个点可能是累加器中的多个值同时加1)。

举例子 :                       

 

编程思路解析:

1.      读取一幅带处理二值图像,最好背景为黑色。

2.      取得源像素数据

3.      根据直线的霍夫变换公式完成霍夫变换,预览霍夫空间结果

4.       寻找最大霍夫值,设置阈值,反变换到图像RGB值空间(程序难点之一)

5.      越界处理,显示霍夫变换处理以后的图像

2017-07-21 19:45:58 weixiankui1997 阅读数 1289

先解释一下何为中线检测:
   程序输入的是一段视频,拍摄的是一条白色直线。

如图所示

要求拟合直线的中线并计算中线到图像中心的距离以及和竖直轴的夹角。


思路很明确: 堆每一帧作如下处理:先滤波,去除噪声,防止后面检测直线时的干扰;然后用Hough检测找到图片中的直线;将得到的直线拟合成一条中线并最后显示出来;然后循环,依次处理每一帧并显示。

然而没想到遇到了不少坑。。。。。。。


一开始的时候先把视频截了个图,计划先对单张照片进行处理,成功之后再处理视频。然而我是全屏播放的条件下截的屏,然后就挖了一个坑。。。。。。

这一步还是比较顺利的(虽然花了我一个晚上的时间。。。。。。),遇到了第一个问题

vector <Vec2f> lines;//the rho and theta of each line
    HoughLines(edge,lines,0.1,CV_PI/360,13,0,0);
我一开始想的拟合方法是对每一条线的rho 和 theta 相加然后取平均值,进而得到中线的rho和theta。theta这么做是没问题的,但是rho就不行了,如果白线左边检测出来1条线,而右边检测出来100条线,这种方法得到的’中线‘肯定会明显偏向右边。

解决思路:对白线的左边和右边分别拟合两条直线,然后再用拟合得到的两条线的rho取平均值得到中线的rho。

解决方法: 新建一个array储存lines的rho,然后对rho进行排序,然后计算相邻元素的差值,当相邻差值出现剧烈变化时判定之后的线属于另外一边。



代码如下

sort(arr_rho,arr_rho+lines.size());
    //for(int i=(total-count);i<total;i++)//because the heading (total-count) values in the array is zero
     //   cout<<"rho"<<arr_rho[i]<<endl;
    double rho_mid_l,rho_mid_r;
    static int divide;
    double count_l=0,count_r=0;
    bool left=1;
    for(int i=(total-count);i<total;i++)
    {
        if((abs(arr_rho[i]-arr_rho[i+1]) > 10*abs(arr_rho[i+1]-arr_rho[i+2]))); 
        {
            left = 0;
            divide = i;
        }
        if(left)
        {
            rho_mid_l += arr_rho[i];
            count_l++;
        }
        else 
        {
            rho_mid_r += arr_rho[i];
            count_r++;
        }
    }
    rho_mid_l += arr_rho[divide];
    count_l++;
    rho_mid_r -= arr_rho[divide];
    count_r--;
    rho_mid_l = rho_mid_l/count_l;
    rho_mid_r = rho_mid_r/count_r;
    rho_mid=(rho_mid_l+rho_mid_r)/2.0;
 

检测这张截图的时候没毛病,不过之后用到视频里面就发现经常出问题,苦思良久才发现是判断条件除了问题

改成如下就好使了

if((abs(arr_rho[i]-arr_rho[i+1]) > 10*abs(arr_rho[i+1]-arr_rho[i+2]))&&left&&(abs(arr_rho[i+1]-arr_rho[i])>10))
&&left 就防止了divide的值不正确的情况(考虑arr_rho[]={100,101,200,200.9,201,201.1},这种情况下divede就会是3,而正确的应该是2)
&&(abs(arr_rho[i+1]-arr_rho[i])>10)) 则避免的这种情况下的误判:arr_rho[]={100,102,100.01,201,202,} 若没有这个条件程序就会判断100后面的rho都是另外一边的了


然后对于有些图片还是会出现中线不准确,甚至没办法画出中线的情况;

经过不断调试明白了opencv里面rho和theta的几何含义:坐标轴原点为左上角,向下为y轴,向右为x轴, theta为弧度角属于[0,CV_PI),顺时针为正方向!rho的符号与取决于直线在原点的上方还是下方,下正上负。

之后因为实际视频的大小比截图的大小小了将近3倍,几乎检测不出来直线(阈值设的太高了)后来又是不断得调节滤波和hough检测的参数,才能检测出中线。

封装成函数也费了一番功夫,不过封装好之后用和调试都舒服多了。

最后贴一下源码吧

/*
* video.cpp
* Author:
*   Weixiankui
*/    
#include <iostream> // for standard I/O
#include <string>   // for strings
#include <iomanip>  // for controlling float print precision
#include <sstream>  // string to number conversion
#include <opencv2/core.hpp>     // Basic OpenCV structures (cv::Mat, Scalar)
#include <opencv2/imgproc.hpp>  // Gaussian Blur
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp> 

using namespace std;
using namespace cv;


double Distance(Point core,Point pt1,Point pt2)
{
    double A,B,C,dis;
    A=pt2.y-pt1.y;
    B=pt1.x-pt2.x;
    C=pt2.x*pt1.y-pt1.x*pt2.y;
    dis=abs(A*core.x+B*core.y+C)/sqrt(A*A+B*B);
    return dis;
}

void MiddleLine(Mat image,Point *points);

int main(int argc,char **argv)
{
    if(argc!=2)
    {
        printf("usage:\n./video <path of video>\n");
        return -1;
    }
    const char *win="video~";
    VideoCapture video(argv[1]);
    namedWindow(win,WINDOW_AUTOSIZE);
    char key=0;
    Mat head;
    video >> head;
    Point core=Point(head.cols*0.5,head.rows*0.5);//to get the center coordinates

    
    for(;;)
    {
        
        Mat src;
        video>>src;
        Point points[2];
        MiddleLine(src,points);//input the image and get two points of the middle line int the Point array(2nd parament)
        line(src,points[0],points[1],Scalar(0,0,255),2,CV_AA);//draw the line
        imshow(win,src);
        double dist = Distance(core,points[0],points[1]);//get the distance between the middle line and the center point
        cout<<"Distance"<<dist<<endl;
        waitKey(1);
        
    }
    return 0;
}

void MiddleLine(Mat image ,Point *points)
{
    //Point points[2];//the two point of the midline to be return;
    Mat src = image.clone();
    int blur_value=5;//modify the blur_value to make the ebge(you can use imshow("edge",edge) to see it) clear
    Mat edge,dst;

    medianBlur(src,src,blur_value+2);
    blur(src,src,Size(blur_value,blur_value));
      
    Canny(src,edge,20,75,3);
    cvtColor(edge,dst,CV_GRAY2BGR);
    //imshow("edge",edge);
    vector <Vec2f> lines;
    HoughLines(edge,lines,0.1,CV_PI/360,13,0,0);
    //cout<<"lines.size()  "<<lines.size()<<endl;
    float theta_mid=0,rho_mid=0;
    int count=0,total=0;
    float arr_rho[lines.size()];
    for(size_t i=0;i<lines.size();i++)//
        arr_rho[i]=0;
    float sum;
    //for(size_t i=0;i<lines.size();i++)
    //{
    //    cout<<"rho  "<<lines[i][0]<<"  theta  "<<lines[i][1]<<endl;
    //}
    //cout<<"------------------------"<<endl;
    for(size_t i=0;i<lines.size();i++)//
    {
        total ++;
        if(abs(lines[i][1]-CV_PI/2.0)<0.5) continue;//
        //float rho=lines[i][0],theta = lines[i][1];
        theta_mid +=lines[i][0]>0?lines[i][1]:(lines[i][1]-CV_PI);
        
        arr_rho[count]=abs(lines[i][0]);
        sum += lines[i][0]*0.1;
        //cout<<"count"<<count<<endl;
        //cout<<"rho"<<arr_rho[count]<<endl;
        count++;

    }
    sort(arr_rho,arr_rho+lines.size());
    //for(int i=(total-count);i<total;i++)//because the heading (total-count) values in the array is zero
     //   cout<<"rho"<<arr_rho[i]<<endl;
    double rho_mid_l,rho_mid_r;
    static int divide;
    double count_l=0,count_r=0;
    bool left=1;
    for(int i=(total-count);i<total;i++)
    {
        if((abs(arr_rho[i]-arr_rho[i+1]) > 10*abs(arr_rho[i+1]-arr_rho[i+2]))&&left&&(abs(arr_rho[i+1]-arr_rho[i])>10))//add the && left condition other wise the divide may be given to numer,and the program would have problem; 
        {
            left = 0;
            divide = i;
    //        cout<<"divide"<<divide<<endl;
        }
        if(left)
        {
            rho_mid_l += arr_rho[i];
            count_l++;
        }
        else 
        {
            rho_mid_r += arr_rho[i];
            count_r++;
        }
    //    cout <<"count_l&count_r"<<count_l<<count_r<<endl;
    }
    rho_mid_l += arr_rho[divide];
    count_l++;
    rho_mid_r -= arr_rho[divide];
    count_r--;
    //cout<<"divide"<<divide<<endl<<"count_l&count_r"<<count_l  << count_r<<endl;
    rho_mid_l = rho_mid_l/count_l;
    //cout << "rho_mid_l" << rho_mid_l<<endl;
    rho_mid_r = rho_mid_r/count_r;
    //cout << "rho_mid_r"<<rho_mid_r<<endl;
    rho_mid=(rho_mid_l+rho_mid_r)/2.0;
    float rho;
    if(sum>0)
        rho=rho_mid;
    else
        rho=-rho_mid;
    rho=theta_mid>0?rho_mid:-rho_mid;
    //theta=theta_mid
    theta_mid=theta_mid/count;
    float theta=theta_mid>0?theta_mid:(CV_PI+theta_mid);
    Point pt1,pt2;
    double a =cos(theta),b=sin(theta);
    double x0=a*rho,y0=b*rho;

    points[0].x=cvRound(x0+10000*(b)); 
    points[0].y=cvRound(y0+10000*(-a));
    points[1].x=cvRound(x0+10000*(-b));
    points[1].y=cvRound(y0+10000*(a));
    cout<<"theta"<<theta<<endl;
    //cout<<"~~~~~~~~~~~~~~~~~~~~~"<<endl;
    //return points;
}

今天把矫正相机的作业写完了,好多线性代数的知识都忘了,从今天起开始看线代的mooc



2016-03-24 15:08:44 qqh19910525 阅读数 424

霍夫变换就是利用参数空间中的(ρ,θ)来表示一条直线,其中ρ是原点到直线的垂直距离,θ是原点到直线的一条垂线段与θ的夹角。通过几何的方法(添加辅助线,相似三角形来做),我们可以证明对于直线上的任何一点都有ρ=xcosθ +ysinθ。


向左转|向右转

知道这个原理之后我们就可以通过便利ρ和θ的值域来对每个点进行试验,即把每个点的坐标,θ带入xcosθ +ysinθ判断是否等于ρ即可。若等于则说明这个点在这条直线上。通过遍历所有的点来对我们的直线(ρ,θ)进行投票。设定一个阈值就可以得到比较明显的(点数较多的)直线。

Hough变换检测直线

阅读数 1112

hough 变换

阅读数 246

没有更多推荐了,返回首页