【数字图像处理】Hough变换C语言实现

(一)Hough圆检测

经过图像的预处理后,我们得到了只保留边缘信息的二值图像,统计可以发现在一个320x240的图像中最后有效的信息点只有5000个左右现在就是通过对这5000个点的统计与运算完成对圆与直线的检测。直线检测最容易受到环境干扰,如果直接对一幅图像进行直线检测,检测出最长的那根直线不一定,是你想要的直线。所以我们首先要进行的是Hough圆的检测。
Hough变换不仅适用于直线检测,还适用于任何形式的f(x,a)=0所表示的图形的检测,其中x 表示坐标向量,a表示系数向量。下边我们对Hough变换检测圆的原理做简要介绍。
对于一个半径为r,圆心为(a,b)的圆,我们将其表示为:

在这里插入图片描述此时x=[x,y]T,a=[a,b,r]T,其参数空间为三维。
显然,图像空间上的一点(x,y),在参数空间中对应着一个圆锥,如下图所示。

在这里插入图片描述
而图像空间的一个圆就对应着这一簇圆锥相交的一个点,这个特定点在参数空间的三维参数一定,就表示一定半径一定圆心坐标的图像空间的那个圆。
上述方法是经典的Hough圆检测方法的原理,它具有精度高,抗干扰能力强等优点,但由于该方法的参数空间为三维,要在三维空间上进行证据累计的话,需要的时间和空间都是庞大的,在实际应用中不适用。

程序设计

Hough变换的具体算法步骤如下:
• 适当的量化参数空间。
• 将参数空间的每一个单元看作一个累加器。
• 初始化累加器为0。
• 对图像空间的每一点,在其所满足参数方程对应的累加器上加1。
• 累加器存储的最大值即为对应的图形的参数。

程序实际思路:

(1) 因为如果直接进行Hough圆的投票的话,我们需要一个3维的表保存行,列,角度信息。我们将这个投票过程分成两个步骤。

(2) 首先完成对于固定半径的圆的投票。然后在对每一个半径的圆的投票进行比较。

1) 建立一张与原图大小一致的投票表,用来表示每个点作为圆心会有多少个点与之匹配:

    int AccuArrLength=img->width * img->height;;
    pAccumulateArr=(int*)malloc(AccuArrLength*sizeof(int));

2)在根据极坐标运算公式,对每一个点可能对应的圆心进行投票,通过遍历每一个点,然后对经过这个点所有的圆的圆心位置进行一次计票

int x = i - R * cos(theta);//得到理想圆心x坐标
int y = j - R * sin(theta);//得到理想圆心y坐标
 if(x>0&&x<img->width&&y>0&&y<img->height&&x<range[1]&&x>range[0]&&y<range[3]&&y>range[2])
 {
      pAccumulateArr[y * img->width + x]++;
  }

3) 找到对应票数最高的圆心位置

    for(int i = 0; i < img->height; i++)
        for(int j = 0; j < img->width; j++)
        {
            int value_ = pAccumulateArr[i * img->width + j];
            if(value_>max_value)
            {
                //printf("(%d,%d,%d,%d)",max_value,value_,i,j);
               max_value=value_;
                circles->x=j;
                circles->y=i;
                circles->r=R;
            }
        }   

然后我们就可以找到想要的圆了。

(3) 对一个半径范围内的所有圆进行一次(1)中的操作,然后,从中选取票数最高的圆,那么这个圆就是我们想要的圆了。

(4) 具体程序请参考Litecv/Imgproc/li_canny.c

(二) Hough直线检测

Hough变换的方法基本思想可以从检测图像中的直线这个简单问题中看到。直线由两点A=(X1,Y1)和B=(X2,Y2)定义,所下图1(a)示。通过点A的所有直线由y1=kx1+q表示,k和q是某些值。这意味着同一个方程可以解释为参数空间k,q的方程。因此通过点A的所须直线可以表示为方程q=-x1k+y1图1.(b)。类似地通过点B的直线可以表示q=-x2*k+y2。在参数空间k和q中,两条直线的唯一公共点是在原图像空间中表示连接点A和B的唯一存在的直线。

在这里插入图片描述
这意味着图像中的每条直线在参数空k,q中由单独一个点表示,直线的任何一部分都变换为同一个点。直线检测的主要思想是确定图中所在的直线像素,将通过这些像素的所在直线变换到参数空间的对应点,在参数空间检测点(a,b),此点是图像中出现的直线y=ax+b的Hough变换的结果。
我们可以注意到直线的参数方程y =kx+q只适合解释Hough变换原理,在检测垂直线条和参数的非线性离散化时会遇到困难。如果直线表示成s=xcosθ+ysinθ。Hough变换就没有这些局限性。直线还是被变换为单个点,因此可用该原理进行直线检测。如下图2所示:

在这里插入图片描述
我们要注意到Hough变换的重要性质是对图像中直线的殘缺部分、噪声以及其它共存的非直线结构不敏感。因为从图像空间到累计空间的变换的鲁棒性引起的,直线殘缺的部分只会造成较低的局部极值。

(三)代码实现

#ifndef LI_CANNY_C
#define LI_CANNY_C




#include "cv.h"
#include "li_image_proc.h"
#include <stdio.h>
#include <math.h>


/**
 * @name: Li_Canny
 * @msg:  参考文章 https://blog.csdn.net/HUSTER_Gy/article/details/102942452?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160498444419724838560446%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160498444419724838560446&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v28-11-102942452.first_rank_ecpm_v3_pc_rank_v2&utm_term=canny%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B%E7%AE%97%E6%B3%95c%E5%AE%9E%E7%8E%B0&spm=1018.2118.3001.4449
 *        图像砍尼检测
 * @param {Li_Image* img 原图像
 *         BYTE CannyType选择算子
 *         BYTE min      最大阈值
 *         BYTE max}     最小阈值
 * @return {*}
 */
LI_API
Li_Image* Li_Canny(Li_Image* img,BYTE CannyType,BYTE min,BYTE max)
{
    if(img==NULL||img->imgdepth!=LI_DEP_8U)return NULL;
    LILOG("CANNY");           
    Li_Image* out =Li_Copy_Image(img); 
    Li_Image* PP =Li_Copy_Image(img); 
    Li_Image* QQ =Li_Copy_Image(img);
    Li_Kernel* SX,*SY;
    double *P =(double*)malloc(sizeof(double)*img->width*img->height);/*x方向偏导*/
    double *Q =(double*)malloc(sizeof(double)*img->width*img->height); /*y方向偏导*/
    /*PI*-1/2* --  PI*1/2*/
    double *Threa =(double*)malloc(sizeof(double)*img->width*img->height);

    #ifdef DEBUG
    Li_Save_Image("before_minus.bmp",out);
    #endif 
    
    /*开始计算梯度与方向角*/
    switch (CannyType)
    {
    case LI_CANNY_MYDEFINE:
        {
            for(int i=0;i<img->height-1;i++)
                for(int j=0;j<img->width-1;j++)
                {
                    BYTE* ptr[4];
                    BYTE* ptr2;
                    /**
                     * 2  3
                     * 0  1
                    */
                    ptr[0]=img->at(img,j,i);
                    ptr[1]=img->at(img,j+1,i);
                    ptr[2]=img->at(img,j,i+1);
                    ptr[3]=img->at(img,j+1,i+1);
                    P[i*img->width+j]=(double)((double)*ptr[3]+*ptr[1]-*ptr[0]-*ptr[2])/2;
                    Q[i*img->width+j]=(double)((double)*ptr[0]+*ptr[1]-*ptr[2]-*ptr[3])/2;  
                    Threa[i*img->width+j]=atan(Q[i*img->width+j]/P[i*img->width+j]);
                    ptr2=out->at(out,j,i);
                    *ptr2=sqrt(P[i*img->width+j]*P[i*img->width+j]+Q[i*img->width+j]*Q[i*img->width+j]);
                }
        }
        break;
    
    case LI_CANNY_SOBEL:
        {
            for(int i=1;i<img->height-1;i++)
                for(int j=1;j<img->width-1;j++)
                {
                    BYTE* ptr[9];
                    BYTE* ptr2;
                    /**6  7  8
                     * 3  4  5
                     * 0  1  2
                     */
                    if(j-1>=0&&i-1>=0)
                    ptr[0]=(BYTE*)img->at(img,j-1,i-1);
                    if(j>=0&&i-1>=0)
                    ptr[1]=(BYTE*)img->at(img,j+0,i-1);
                    if(j+1<=img->width&&i-1>=0)
                    ptr[2]=(BYTE*)img->at(img,j+1,i-1);
                    if(j-1>=0&&i>=0)
                    ptr[3]=(BYTE*)img->at(img,j-1,i+0);
                    if(j>=0&&i>=0)
                    ptr[4]=(BYTE*)img->at(img,j+0,i+0);
                    if(j+1<=img->width&&i>=0)
                    ptr[5]=(BYTE*)img->at(img,j+1,i+0);
                    if(j-1>=0&&i+1<=img->height)
                    ptr[6]=(BYTE*)img->at(img,j-1,i+1);
                    if(j>=0&&i+1<=img->height)
                    ptr[7]=(BYTE*)img->at(img,j+0,i+1);
                    if(j+1<=img->width&&i+1<=img->height)
                    ptr[8]=(BYTE*)img->at(img,j+1,i+1);

                    P[i*img->width+j]=(double)((double)*ptr[2]+*ptr[5]*2+*ptr[8]-*ptr[0]-*ptr[3]*2-*ptr[6]);
                    Q[i*img->width+j]=(double)((double)*ptr[6]+*ptr[7]*2+*ptr[8]-*ptr[0]-*ptr[1]*2-*ptr[2]);    
                    Threa[i*img->width+j]=atan(Q[i*img->width+j]/P[i*img->width+j]);
                    ptr2=out->at(out,j,i);
                    *ptr2=sqrt(P[i*img->width+j]*P[i*img->width+j]+Q[i*img->width+j]*Q[i*img->width+j]);
                }
        }
        break;

        case LI_CANNY_ROBERTS:
         {
            for(int i=0;i<img->height-1;i++)
                for(int j=0;j<img->width-1;j++)
                {
                    BYTE* ptr[4];
                    BYTE* ptr2;
                    /**
                     * 2  3
                     * 0  1
                    */
                    ptr[0]=img->at(img,j,i);
                    ptr[1]=img->at(img,j+1,i);
                    ptr[2]=img->at(img,j,i+1);
                    ptr[3]=img->at(img,j+1,i+1);
                    ptr2=out->at(out,j,i);
                    *ptr2=abs(*ptr[2]-*ptr[1])+abs(*ptr[0]-*ptr[3]);
                    Threa[i*img->width+j]=atan(abs(*ptr[2]-*ptr[1])/abs(*ptr[0]-*ptr[3]));
                }         
         }
        break;

    case LI_CANNY_PREWITT:
          {
            for(int i=1;i<img->height-1;i++)
                for(int j=1;j<img->width-1;j++)
                {
                    BYTE* ptr[9];
                    BYTE* ptr2;
                    /**6  7  8
                     * 3  4  5
                     * 0  1  2
                     */
                    if(j-1>=0&&i-1>=0)
                    ptr[0]=(BYTE*)img->at(img,j-1,i-1);
                    if(j>=0&&i-1>=0)
                    ptr[1]=(BYTE*)img->at(img,j+0,i-1);
                    if(j+1<=img->width&&i-1>=0)
                    ptr[2]=(BYTE*)img->at(img,j+1,i-1);
                    if(j-1>=0&&i>=0)
                    ptr[3]=(BYTE*)img->at(img,j-1,i+0);
                    if(j>=0&&i>=0)
                    ptr[4]=(BYTE*)img->at(img,j+0,i+0);
                    if(j+1<=img->width&&i>=0)
                    ptr[5]=(BYTE*)img->at(img,j+1,i+0);
                    if(j-1>=0&&i+1<=img->height)
                    ptr[6]=(BYTE*)img->at(img,j-1,i+1);
                    if(j>=0&&i+1<=img->height)
                    ptr[7]=(BYTE*)img->at(img,j+0,i+1);
                    if(j+1<=img->width&&i+1<=img->height)
                    ptr[8]=(BYTE*)img->at(img,j+1,i+1);

                    P[i*img->width+j]=(double)((double)*ptr[2]+*ptr[5]+*ptr[8]-*ptr[0]-*ptr[3]-*ptr[6]);
                    Q[i*img->width+j]=(double)((double)*ptr[6]+*ptr[7]+*ptr[8]-*ptr[0]-*ptr[1]-*ptr[2]); 
                    Threa[i*img->width+j]=atan(Q[i*img->width+j]/P[i*img->width+j]);   
                    ptr2=out->at(out,j,i);
                    *ptr2=sqrt(P[i*img->width+j]*P[i*img->width+j]+Q[i*img->width+j]*Q[i*img->width+j]);
                }
        }      

        break;
    
    default:
        break;
    }

    #ifdef DEBUG
    Li_Save_Image("after_minus.bmp",out);
    #endif 

    /*非极大值抑制*/
    for(int j=1;j<out->height-1;j++)
        for(int i=1;i<out->width-1;i++)
        {
            double t=Threa[j*img->width+i];
            BYTE* ptr=out->at(out,i,j);
            double g=(double) *ptr;
            double g0, g1;
            if ((t >= -(3*M_PI/8)) && (t < -(M_PI/8)))
            {
                ptr=out->at(out,i-1,j-1);
                 g0=(double) *ptr;
                ptr=out->at(out,i+1,j+1);
                 g1=(double) *ptr;
            } 
            else if ((t >= -(M_PI/8)) && (t < M_PI/8))
            {
                ptr=out->at(out,i-1,j);
                 g0=(double) *ptr;
                ptr=out->at(out,i+1,j);
                 g1=(double) *ptr;
            }
            else if ((t >= M_PI/8) && (t < 3*M_PI/8))
            {
                ptr=out->at(out,i+1,j-1);
                 g0=(double) *ptr;
                ptr=out->at(out,i-1,j+1);
                 g1=(double) *ptr;
            }
            else
            {
                ptr=out->at(out,i,j-1);
                 g0=(double) *ptr;
                ptr=out->at(out,i,j+1);
                 g1=(double) *ptr;                
            }
        
            if (g <= g0 || g <= g1) {
                ptr=out->at(out,i,j);
                *ptr=0;
            }

        }
 
    /*阈值化操作*/
    #ifdef DEBUG
    Li_Save_Image("before_thre.bmp",out);
    #endif 
    Li_Image*out1=Li_Double_Threshold(out,min,max);
    #ifdef DEBUG
    Li_Save_Image("after_thre.bmp",out1);
    #endif 

    /*边缘链接*/
        for (int j = 1; j < out1->height-2; j++) 
        for (int i = 1; i < out1->width-2; i++) {
            BYTE* ptr=out1->at(out1,i,j);
            if(*ptr==255)
            {
                for (int m = -1; m < 1; m++) {
                    for (int n = -1; n < 1; n++) {
                    BYTE* temp=out1->at(out1,i+n,j+m);
                        if(*ptr!=0&&*ptr!=255)
                        *ptr=255;
                    }
                }             
            }
        }

        for (int j = 0; j < out1->height-1; j++) {
        for (int i = 0; i < out1->width-1; i++) {
            // 如果该点依旧是弱边缘点,及此点是孤立边缘点
             BYTE* ptr=out1->at(out1,i,j);
             if(*ptr!=255&&*ptr!=0)
             *ptr=0;
            }
        }
    Li_Destroy_Image(PP);
    Li_Destroy_Image(QQ);
    return out1;
}

#endif // !LI_CANNY_C

(四)效果展示

在这里插入图片描述

(五)写在后面

因为LiteCV项目才刚刚写了一个开头,代码中有错误的地方还望指出。我已经将项目同步到了github,我会实时更新这个代码仓库。
项目github地址:
LiteCV

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与光同程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值