文章目录

  • (一)Hough圆检测
    • 程序设计
    • (1) 因为如果直接进行Hough圆的投票的话,我们需要一个3维的表保存行,列,角度信息。我们将这个投票过程分成两个步骤。
    • (2) 首先完成对于固定半径的圆的投票。然后在对每一个半径的圆的投票进行比较。
    • (3) 对一个半径范围内的所有圆进行一次(1)中的操作,然后,从中选取票数最高的圆,那么这个圆就是我们想要的圆了。
    • (4) 具体程序请参考Litecv/Imgproc/li_canny.c
  • (二) Hough直线检测
  • (三)代码实现
  • (四)效果展示
  • (五)写在后面

(一)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 DEBUGLi_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 DEBUGLi_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 DEBUGLi_Save_Image("before_thre.bmp",out);#endif Li_Image*out1=Li_Double_Threshold(out,min,max);#ifdef DEBUGLi_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

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

  1. 数字图像处理—亮度变换与空间滤波—亮度变换函数

    数字图像处理-亮度变换与空间滤波-亮度变换函数 参考资料:<数字图像处理>,仅供学习参考. 亮度变换函数仅取决于亮度的值,与像素位置无关,所以亮度变换函数通常可写成如下形式: s=T(r) ...

  2. hough变换连接边缘matlab,matlab图像处理hough变换程序执行问题

    matlab图像处理hough变换程序执行问题0 pxkd82013.04.23浏览184次分享举报 程序如下: I= imread('D:\MATLAB7\fenkuai.bmp','bmp');% ...

  3. 数字图像处理--空间变换

    上次讲了数字图像处理的一题,今天再贴一题 Geometric transform (test image: fig3.tif) Develope geometric transform program ...

  4. MATLAB--数字图像处理 Hough变换

    前言 Hough变换是1962年由Hough提出来的,用于检测图像中直线.圆.抛物线.椭圆等形状能够用一定函数关系描述的曲线. 在这里我们重点研究的是利用Hough变换检测图中的直线. Hough变换 ...

  5. 【数字图像处理】BMP图片的读取显示存储(C语言实现)

    (一)背景介绍 这段时间接到了一个新活,是关于图像处理的一个探地摄像头的项目.所以也差不多是时候开始学习一下数字图像处理的知识了.本来我们的方案是直接移植opencv,编译一下以后其他就基本啥都不用管 ...

  6. 数字图像处理-空间域图像增强(一)(图像反转,对数变换,幂次变换、分段线性变换)

    空间域增强的第一部分:图像反转,对数变换,幂次变换.分段线性变换 (s:现点值,r: 原点值) 图像反转: 这个无需多说,就是把黑变白,白变黑,拿八位灰度图像来说 表达式:s=255-r 作用:看清暗 ...

  7. C语言数字图像处理编程

    C语言数字图像处理 读取bmp图像并做简单显示 bmp图像几何变换(移动,旋转,镜像,转置,缩放) 彩色图像转灰度图,灰度图反色 图像中值滤波与平均滤波 bmp图像锐化 图像的半影调和抖动技术 bmp ...

  8. matlab 霍特林变换,数字图像处理(第3版面向CS2013计算机专业规划教材)

    导语 内容提要 姚敏编著的<数字图像处理(第3版面向CS2013计算机专业规划教材)>详细介绍了数字图像处理的基本理论.主要技术和最新进展.全书共分13章,内容主要包括图像获取.图像变换. ...

  9. 基于标准C语言的数字图像处理基本框架(转)

    考虑到现有的数字图像处理都是基于Windows平台,都或多或少使用了Win32 API函数,不能移植到Linux或者嵌入式系统中.为了使程序可移植,采用标准C语言建立了数字图像处理的基本框架,如下图所 ...

  10. 数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF

    数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF 一.概述 参考:特征点匹配+特征检测方法汇总 ORB的全称是Oriented ...

最新文章

  1. centos7.3允许mysql远程连接_Centos7.3 安装Mysql和远程登录到Mysql-Go语言中文社区
  2. ajax方式下载文件
  3. [蓝桥杯]算法提高 天天向上(记忆化搜索)
  4. 如何导出数据到Excel表格
  5. 同前端联调过程中遇到的坑
  6. Reporting Services 在WIN7和2008下出现“授予的权限不足,无法执行此操作。 (rsAccessDenied)”的解决办...
  7. 简约好看导航栏(HTML、CSS)
  8. Simulink模块之VCO(压控振荡器)
  9. 仿小米商城html网页源码
  10. 熟悉Jeecg框架、学会环境的搭建及前后端项目(OA)的启动
  11. Java链表-链表反转
  12. 【独行秀才】macOS Big Sur 11.6.5正式版(20G517)原版镜像
  13. 计算机界的传奇人物:高纳德
  14. 会议记录转文字 - 语音识别
  15. Elasticsearch 文档内容检索插件 ingest attachment 安装
  16. 在浏览器地址栏输入一个URL后回车,执行的全部过程
  17. B2B网页付款时,出现“对不起,未检测到签名控件”,不能正常支付
  18. 百度脱壳的一点尝试--人肉修复
  19. JS-小案例 关于时间--实时时间
  20. 双线机房双网卡双ip DNSpod智能DNS解析和路由设置

热门文章

  1. python文件操作的几种方法with open,r,w,b
  2. 【windows7 bluescreen蓝屏的解决方法】
  3. nsstring sizewithfont的崩溃
  4. 在WordPress网站上添加鼠标点击特效和网页背景特效
  5. html期末设计作业——品牌红酒销售网页模板(4页) html网页设计期末大作业_网页设计平时作业
  6. 二 关键词---关键词扩展(五)
  7. php如何获取视频文件分辩率,视频分辨率怎么看 查看视频分辨率、帧率、尺寸、以及编码器等详细参数...
  8. Python爬取链家二手房数据写入csv文件
  9. 云原生微服务架构实战精讲第三节 示例用户场景分析和领域驱动DDD
  10. 了解DPDK——内核NIC接口