OpenCV图像处理篇之Hough变换

图像空间到参数空间的转换

对于图像中共线的点集{(x0,y0), (x1,y1), ...}都经过直线y=kx+b,先在我们换一个说法,“斜率为k,截距为b的直线y=kx+b包含了所有在该直线上的点”。一种强调的是图像中的点集,另一种强调的是直线的参数k和b,通过直线的点集去描述这条直线明显没有直接通过k,b两个参数去描述那样直接方便。而Hough变换就是将我们“点共线”的思维转化到参数空间{k,b}进行描述,图像空间中所有经过y=kx+b的点经过Hough变换后在参数空间都会相交于点(k,b),这样,通过Hough变换,就可以将图像空间中直线的检测转化为参数空间中对点的检测。我们不妨将y=kx+b进行一下变形:

这就是Hough变换将图像空间坐标(x,y)转化为参数空间(k,b)的Hough变换式。

Hough变换的步骤(执行过程):

  1. 在参数空间中建立一个二维(分别对应k,b)计数器,实际就是二维数组kbcnt,k维度为图像中直线斜率可能范围,b维度为图像中截距可能范围;数组中所有值都初始化为0;
  2. 扫描图像空间中的所有点(xi,yi),Hough变换式进行图像空间到参数空间的变换(ki,bi),计数kbcnt(ki,bi)++
  3. 设定阈值thr(图像中有多少个点共线才认为存在直线),kbcnt(ki,bi)>thr的ki,bi组成图像中的直线y=ki*x+bi

然而,上面的检测直线的方案貌似还有些问题:如果图像中存在竖直的直线呢,那kbcnt的k维度岂不是要无穷大!因此,才有了另一种参数空间的方案:利用极坐标参数而非“斜率-截距式”描述直线。



极坐标中的直线表示

极坐标中的直线方程为

将其改写成Hough变换式,即自变量(x,y)到参数变量(r,theta)的映射:

使用极坐标参数空间,Hough变换的步骤不变,只不过将kbcnt替换成rthcnt,r范围是图像对角线的长度,th范围是0~2*pi。因为图像是离散的,所以r和th都有一个步进值dr和dth。

Hough变换除了检测直线,还可用来检测任何能用数学表达式表示的形状,如最常见的圆、椭圆,基本原理都是将图像空间的像素转变到参数空间,然后在参数空间中对共线/圆/椭圆的点进行统计,最后通过阈值判决是否是符合要求的形状。

http://en.wikipedia.org/wiki/Hough_transform 上对Hough变换的内容有更多的描述。

OpenCV中的Hough变换

/*
 * FileName : hough.cpp
 * Author   : xiahouzuoxin @163.com
 * Version  : v1.0
 * Date     : Wed 26 Nov 2014 09:52:45 PM CST
 * Brief    :
 *
 * Copyright (C) MICL,USTB
 */
#include "cv.h"
#include "highgui.h"
#include "opencv2/imgproc/imgproc.hpp"

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc < 2) {
        cout<<"Usage: ./hough [image file name]"<<endl;
        return -1;
    }

    Mat src = imread(argv[1], CV_LOAD_IMAGE_COLOR);
    if (!src.data) {
        cout<<"Read image error"<<endl;
        return -1;
    }
    namedWindow("Source", CV_WINDOW_AUTOSIZE);
    imshow("Source", src);

    Mat img;
    cvtColor(src, img, CV_RGB2GRAY);
    GaussianBlur(img, img, Size(3,3), 0, 0);
    Canny(img, img, 100, 200, 3);
    namedWindow("Canny", CV_WINDOW_AUTOSIZE);
    imshow("Canny", img);

    vector<Vec2f> lines;
    HoughLines(img, lines, 1, CV_PI/360, 200);  // 返回直线坐标对
    for (size_t i=0; i<lines.size(); i++) {
        float rho = lines[i][0];
        float theta = lines[i][1];
        Point pt1,pt2;
        double a=cos(theta);
        double b=sin(theta);
        double x0 = rho*a;
        double y0 = rho*b;
        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(src, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
    }
    namedWindow("Hough", CV_WINDOW_AUTOSIZE);
    imshow("Hough", src);

    waitKey();

    return 0;
}

在做Hough变换之前,一般都要先使用LOG或Canny先检测边缘,再对边缘图像进行Hough变换操作,上面程序使用Canny算子检测边缘,Canny算子Canny(img, img, 100, 200, 3);的两个阈值100,100选择很重要,间接影响Hough检测的结果,同时HoughLines中的阈值参数也应该细调。用上面程序对道路直线进行检测结果如下,



道路图片1



Canny算子边缘检测结果



Hough直线检测结果



道路图片2



Canny算子边缘检测结果



Hough直线检测结果

Hough变换源码分析

Hough变换的源代码在modules/imgproc/src/hough.cpp中,提供了3种Hough变换源码:直线检测、概率Hough变换检测直线、圆检测,如果要实现其它有解析方程的图形的检测,则要自己动手写了。



Hough变换调用接口函数解释

先看Hough检测直线的代码,cvHoughLines2也只不过是个对不同Hough方法的封装,下面是该函数中的部分代码,选择不同的Hough变换方法,

switch( method )
{
case CV_HOUGH_STANDARD:
      icvHoughLinesStandard( img, (float)rho,
            (float)theta, threshold, lines, linesMax );      // 标准Hough变换
      break;
case CV_HOUGH_MULTI_SCALE:
      icvHoughLinesSDiv( img, (float)rho, (float)theta,
            threshold, iparam1, iparam2, lines, linesMax );  // 多尺度Hough变换
      break;
case CV_HOUGH_PROBABILISTIC:
      icvHoughLinesProbabalistic( img, (float)rho, (float)theta,
            threshold, iparam1, iparam2, lines, linesMax );  // 概率Hough变换
      break;
default:
    CV_Error( CV_StsBadArg, "Unrecognized method id" );
}

不妨详细看看标准Hough变换的实现代码,

/* 这段注释解释了函数各个参数的作用
Here image is an input raster;
step is it‘s step; size characterizes it‘s ROI;
rho and theta are discretization steps (in pixels and radians correspondingly).
threshold is the minimum number of pixels in the feature for it
to be a candidate for line. lines is the output
array of (rho, theta) pairs. linesMax is the buffer size (number of pairs).
Functions return the actual number of found lines.
*/
static void
icvHoughLinesStandard( const CvMat* img, float rho, float theta,
                       int threshold, CvSeq *lines, int linesMax )
{
    cv::AutoBuffer<int> _accum, _sort_buf;    // _accum:计数用数组,_sort_buf,排序用数组
    cv::AutoBuffer<float> _tabSin, _tabCos;   // 提前计算sin与cos值,避免重复计算带来的计算性能下降

    const uchar* image;
    int step, width, height;
    int numangle, numrho;
    int total = 0;
    float ang;
    int r, n;
    int i, j;
    float irho = 1 / rho;   // rho指像素精度,常取1,因此irho常为1
    double scale;

    CV_Assert( CV_IS_MAT(img) && CV_MAT_TYPE(img->type) == CV_8UC1 );

    image = img->data.ptr;
    step = img->step;
    width = img->cols;
    height = img->rows;

    numangle = cvRound(CV_PI / theta);  // 根据th精度计算th维度的长度
    numrho = cvRound(((width + height) * 2 + 1) / rho);  // 根据r精度计算r维度的长度

    _accum.allocate((numangle+2) * (numrho+2));
    _sort_buf.allocate(numangle * numrho);
    _tabSin.allocate(numangle);
    _tabCos.allocate(numangle);
    int *accum = _accum, *sort_buf = _sort_buf;
    float *tabSin = _tabSin, *tabCos = _tabCos;

    memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) );

    for( ang = 0, n = 0; n < numangle; ang += theta, n++ )   // 计算三角函数表,避免重复计算
    {
        tabSin[n] = (float)(sin(ang) * irho);
        tabCos[n] = (float)(cos(ang) * irho);
    }

    // stage 1. fill accumulator
    for( i = 0; i < height; i++ )
        for( j = 0; j < width; j++ )
        {
            if( image[i * step + j] != 0 )
                for( n = 0; n < numangle; n++ )
                {
                    r = cvRound( j * tabCos[n] + i * tabSin[n] );  // Hough极坐标变换式
                    r += (numrho - 1) / 2;
                    accum[(n+1) * (numrho+2) + r+1]++;  // 计数器统计
                }
        }

    // stage 2. find local maximums
    for( r = 0; r < numrho; r++ )
        for( n = 0; n < numangle; n++ )
        {
            int base = (n+1) * (numrho+2) + r+1;
            if( accum[base] > threshold &&             // 大于阈值,且是局部极大值
                accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] &&
                accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] )
                sort_buf[total++] = base;
        }

    // stage 3. sort the detected lines by accumulator value
    icvHoughSortDescent32s( sort_buf, total, accum );

    // stage 4. store the first min(total,linesMax) lines to the output buffer
    linesMax = MIN(linesMax, total);  // linesMax是输入参数,表示最多输出多少个直线参数
    scale = 1./(numrho+2);
    for( i = 0; i < linesMax; i++ )
    {
        CvLinePolar line;           // 输出结构,就是(r,theta)
        int idx = sort_buf[i];
        int n = cvFloor(idx*scale) - 1;
        int r = idx - (n+1)*(numrho+2) - 1;
        line.rho = (r - (numrho - 1)*0.5f) * rho;
        line.angle = n * theta;
        cvSeqPush( lines, &line );  // 确定的直线入队列输出
    }
}

Hough.cpp中对输出结构的定义为:

typedef struct CvLinePolar
{
    float rho;
    float angle;
} CvLinePolar;

其它的Hough变换采用类似的方式逐层可以分析其源码,不妨自己试试?

时间: 2024-11-06 07:53:34

OpenCV图像处理篇之Hough变换的相关文章

OpenCV图像处理篇之腐蚀与膨胀

转载请注明出处:http://xiahouzuoxin.github.io/notes 腐蚀与膨胀 腐蚀和膨胀是图像的形态学处理中最基本的操作,之后遇见的开操作和闭操作都是腐蚀和膨胀操作的结合运算.腐蚀和膨胀的应用非常广泛,而且效果还很好: 腐蚀可以分割(isolate)独立的图像元素,膨胀用于连接(join)相邻的元素,这也是腐蚀和膨胀后图像最直观的展现 去噪:通过低尺寸结构元素的腐蚀操作很容易去掉分散的椒盐噪声点 图像轮廓提取:腐蚀操作 图像分割 等等...(在文后给出一则简单实用膨胀操作提

OpenCV图像处理篇之图像平滑

图像平滑算法 图像平滑与图像模糊是同一概念,主要用于图像的去噪.平滑要使用滤波器,为不改变图像的相位信息,一般使用线性滤波器,其统一形式如下: 其中h称为滤波器的核函数,说白了就是权值.不同的核函数代表不同的滤波器,有不同的用途. 在图像处理中,常见的滤波器包括: 归一化滤波器(Homogeneous blur) 也是均值滤波器,用输出像素点核窗口内的像素均值代替输出点像素值. 高斯滤波器(Guassian blur) 是实际中最常用的滤波器,高斯滤波是将输入数组的每一个像素点与 高斯内核 卷积

OpenCV图像处理篇之边缘检测算子

3种边缘检测算子 灰度或结构等信息的突变位置是图像的边缘,图像的边缘有幅度和方向属性,沿边缘方向像素变化缓慢,垂直边缘方向像素变化剧烈.因此,边缘上的变化能通过梯度计算出来. 一阶导数的梯度算子 对于二维的图像,梯度定义为一个向量, Gx对于x方向的梯度,Gy对应y方向的梯度,向量的幅值本来是 mag(f)?=?(Gx2?+?Gy2)1/2,为简化计算,一般用mag(f)=|Gx|+|Gy|近似,幅值同时包含了x而后y方向的梯度信息.梯度的方向为 α?=?arctan(Gx/Gy) . 由于图像

OpenCV图像处理篇之阈值操作函数

阈值操作类型 这5种阈值操作类型保留opencv tutorials中的英文名称,依次为: Threshold Binary:即二值化,将大于阈值的灰度值设为最大灰度值,小于阈值的值设为0. Threshold Binary, Inverted:将大于阈值的灰度值设为0,大于阈值的值设为最大灰度值. Truncate:将大于阈值的灰度值设为阈值,小于阈值的值保持不变. Threshold to Zero:将小于阈值的灰度值设为0,大于阈值的值保持不变. Threshold to Zero, In

OpenCV图像处理篇之边缘检測算子

3种边缘检測算子 灰度或结构等信息的突变位置是图像的边缘,图像的边缘有幅度和方向属性.沿边缘方向像素变化缓慢,垂直边缘方向像素变化剧烈.因此,边缘上的变化能通过梯度计算出来. 一阶导数的梯度算子 对于二维的图像.梯度定义为一个向量. Gx对于x方向的梯度,Gy相应y方向的梯度,向量的幅值本来是 mag(f)?=?(Gx2?+?Gy2)1/2,为简化计算,一般用mag(f)=|Gx|+|Gy|近似,幅值同一时候包括了x而后y方向的梯度信息.梯度的方向为 α?=?arctan(Gx/Gy) . 因为

OpenCV图像处理篇之采样金字塔

转载请注明出处:http://xiahouzuoxin.github.io/notes 图像金字塔 图像金字塔是通过将原始图像经过平滑.下采样所生成一系列具有不同分辨率的图像的集合.金字塔结构(Pyramid)适于多分辨率处理的一种图像存储数据结构. 最常用的生成图像金字塔的方法是采用高斯函数平滑图像,每次将分辨率降低为原来的一半,由此得到一个图像序列{ML,ML-1,--,M0},图像金字塔的存储量为N^2*(1+1/4+1/16+...)=(4*N^2)/3. 如上图:最右边为原始图像,从右

Python下opencv使用笔记(十一)(详解hough变换检测直线与圆)

在数字图像中,往往存在着一些特殊形状的几何图形,像检测马路边一条直线,检测人眼的圆形等等,有时我们需要把这些特定图形检测出来,hough变换就是这样一种检测的工具. Hough变换的原理是将特定图形上的点变换到一组参数空间上,根据参数空间点的累计结果找到一个极大值对应的解,那么这个解就对应着要寻找的几何形状的参数(比如说直线,那么就会得到直线的斜率k与常熟b,圆就会得到圆心与半径等等). 关于hough变换,核心以及难点就是关于就是有原始空间到参数空间的变换上.以直线检测为例,假设有一条直线L,

Hough变换-理解篇

Hough变换-理解篇 霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,它通过一种投票算法检测具有特定形状的物体.该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果.霍夫变换于1962年由Paul Hough 首次提出[53],后于1972年由Richard Duda和Peter Hart推广使用[54],经典霍夫变换用来检测图像中的直线,后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆. 霍夫变换运用两个坐标空间之间的变换

Opencv图像识别从零到精通(22)-----hough变换检测直线与圆

今天要看的是霍夫变换,常用用来检测直线和圆,这里是把常见的笛卡尔坐标系转换成极坐标下,进行累计峰值的极大值,确定.HoughLines,HoughLinesP,HoughCircles,三个函数,首先先看看原理,最后会用漂亮的matlab图,来回归一下,霍夫直线变换. 霍夫线变换: 众所周知, 一条直线在图像二维空间可由两个变量表示. 例如: 在 笛卡尔坐标系: 可由参数:  斜率和截距表示. 在 极坐标系: 可由参数:  极径和极角表示 对于霍夫变换, 我们将用 极坐标系 来表示直线. 因此,