zoukankan      html  css  js  c++  java
  • 学习笔记-霍夫变换

    霍夫变换


    声明:本篇文章要求有一点python基础、了解直线和圆的数学方程,如果学过高等数学(立体几何部分)更佳。
     
    1、Hough变换的算法思想
    2、直线检测
    3、圆检测

     一、Hough变换的算法思想

      Hough变换是图像处理中从图像中识别几何形状的基本方法之一。Hough变换的基本原理在于利用点与线的对偶性,将原始图像空间的给定的曲线通过曲线表达形式变为参数空间的一个点。这样就把原始图像中给定曲线的检测问题转化为寻找参数空间中的峰值问题。也即把检测整体特性转化为检测局部特性。比如直线、椭圆、圆、弧线等。
    霍夫变换于1962年由Paul Hough 首次提出,后于1972年由Richard Duda和Peter Hart推广使用,经典霍夫变换用来检测图像中的直线,后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆。
     
      霍夫变换的数学理解是“换位思考”,比如一条直线y=a*x+b有两个参数,在给定坐标系下,这条直线就可以用a和b进行完整的表述。如果我们把下x和y看作参数,把a和b看作变量的话,理论上来说,只需两个(x,y)就能把a、b确定出来(两个二元一次方程),这个思想跟高等数学中设直线束方程求直线,设平面束方程求平面是一样的。检测圆或者其他形状(能用有限个参数描述的形状)都是这个原理。
     

    二、直线检测

     
      如果想在一张图上检测一条直线,那么第一步应该是获得(至少)两个坐标点(x,y)。如何获得坐标点?在图片上做标记。如何做标记?检测边缘。
      根据常识,一个形状必然有边界,边界具有灰度值剧烈突变的性质,我们可以通过检测边缘的算法,把边缘检测出 来。检测边缘的算法如canny算法等。边缘检测得出的点要尽可能准确,在一条直线上,因为这些点是是我们的坐标点(x,y)。边缘检测结果用二值图保存。用边缘检测的点带入y=a*x+b,并以a,b为横纵坐标,我们可以得到一个参数平面图,有多少点就会在这个参数平面划多少直线。最重要的一点是,只要是一条直线上的点,其参数(a,b)相同,那么在参数空间内就会表现为交于同一点。因此我们在参数空间内,设定一个阈值T,当某点被至少T条直线穿过,就认为这个点(a,b)决定了一条直线。
     
      在实际工程中,由于以y=a*x+b形式计算会出现斜率无穷大的情况,所以换成这种表现形式:ρ= x*cosθ+ y*sinθ,其参数平面为( ρ,θ),他的分解原理如下图(opencv-python默认的坐标系画法)所示:
      函数的具体工作流程为:
    1.   初始化一个二维累加器并设为0,累加器长度为max{θ的取值范围,ρ的取值范围},ρ最大取对角线的像素数。
    2.   选取(x,y)代入直线方程,对θ以步长s(比如1)从0到180遍历并求出ρ值,将累加器相应位置( ρ,θ) +1。
    3.   重复2过程,直到遍历完所有的点。
    4.        以阈值T筛选二位累加器内符合条件的参数对( ρ,θ),画图显示之。

      如果画出来参数空间图,应该是下图这样,右图的两个亮点就代表了左图的两条直线的参数( ρ,θ)。(from:https://en.wikipedia.org/wiki/Hough_transform

      另一个可供观看的动图例子:http://homepages.inf.ed.ac.uk/amos/hough.html

      代码实现:

    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    # 1.准备工作
    # 生成一张两直线的图片
    img=np.zeros((512,512,1), np.uint8)
    cv2.line(img,(0,0),(511,511),255,20)
    cv2.line(img,(0,511),(511,0),255,20)
    # 显示
    cv2.imshow('image',img)
    cv2.waitKey(0)
    # 保存
    cv2.imwrite("images/2_lines.jpg",img)
    
    # 2.寻找边缘
    img=cv2.imread("images/2_lines.jpg")
    #img=cv2.imread("images/1.jpg")
    img_edge = cv2.Canny(img, 50, 150)
    # 边缘显示
    # cv2.imshow('image',img_edge)
    # cv2.waitKey(0)
    
    # 3.霍夫变换
    lines = cv2.HoughLines(img_edge,10,np.pi/180,500)
    
    for i in range(len(lines)):
        rho=lines[i][0][0]
        theta=lines[i][0][1]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))
        cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
    cv2.imshow('image',img)
    cv2.waitKey(0)
    View Code
     

    三、圆检测

      圆的检测也是同一个原理,但圆有三个参数:圆心(a,b)和半径r。使用的方程为(x-a)+(y-b)2=r2。参数空间也相应的为三维空间坐标系,计算量是比较大的。实际实现的时候,可以通过设定很多限制条件来减少计算量。比如opencv-python包里的霍夫圆检测函数cv2.HoughCircles(image,method,dp,minDist[, circles[,param1, param2[,minRadius[,maxRadius]]]]]),其参数说明如下:
         必须参数:

    image:         输入图像。
    method:       固定填cv2.HOUGH_GRADIENT。这是霍夫圆检测的一种数学方法:梯度法,目前只有这一种已经实现。
    dp:          计数器的分辨率图像像素分辨率与参数空间分辨率的比值(官方文档上写的是图像分辨率与累加器分辨率的比值,它把参数空间认为是一个累加器,毕竟里面存储的都是经过的像素点的数量),dp=1,则参数空间与图像像素空间(分辨率)一样大,dp=2,参数空间的分辨率只有像素空间的一半大,这个参数决定了参数的与分辨率相比的精度,一般设置为1,就是参数能精确到一个像素。类似于霍夫直线检测中θ的步长s。
    minDist:      圆心之间最小距离,如果距离太小,会产生很多相交的圆,如果距离太大,则会漏掉正确的圆,防止检测出太多的圆。这一步需要先验知识:你已经知道图像中的圆最小相距多远。

    默认参数:
    param1:      canny检测的双阈值中的高阈值,低阈值是它的一半,这涉及canny算法。
    param2:      最小投票数(基于圆心的投票数),就类似于霍夫直线检测中的阈值T,它决定了某组参数是不是有圆,这个参数也得慢慢试,或者需要一些先验知识。
    minRadius: 需要检测圆的最小半径,需要先验知识。
    maxRadius:需要检测圆的最大半径,需要先验知识。

      上效果图

     

    代码实现:

     1 import cv2
     2 import numpy as np
     3 import matplotlib.pyplot as plt
     4 
     5 # 1.寻找边缘
     6 img=cv2.imread("images/3_circles.png")
     7 img_edge = cv2.Canny(img, 50, 150)
     8 # # 边缘显示
     9 # cv2.imshow('image',img_edge)
    10 # cv2.waitKey(0)
    11 # 2.霍夫变换
    12 circles = cv2.HoughCircles(img_edge,cv2.HOUGH_GRADIENT,1,100,param1=50,param2=30,minRadius=10,maxRadius=200)
    13 # 3.画出检测到的圆
    14 for i in circles[0,:]:
    15     cv2.circle(img,(i[0],i[1]),i[2],(0,0,255),2)  # 圆
    16     cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)     # 圆心
    17     
    18 cv2.imshow('image',img)
    19 cv2.waitKey(0)
    View Code

       

        最后,还是要说说这个算法的优缺点。算法在数学上很漂亮,但计算量随参数个数指数级上升,直线检测需要两个参数,圆检测需要3个,椭圆检测需要5个,一般椭圆检测就需要一些快速搜索参数的算法,前面cv2.HoughCircles()的默认参数部分就是用来减小参数搜索范围的,另一个缺点就是受噪声影响大,太多的噪声会让霍夫变换彻底失去作用,这也是这类从数学原理出发的算法的老毛病:”过度精确“从而缺乏鲁棒性。所以如果输入图像质量还可以,用来检测圆形或者直线边缘还是挺好用的。

    参考资料:

    http://www.cnblogs.com/AndyJee/p/3805594.html

    https://blog.csdn.net/yuyuntan/article/details/80141392

    https://blog.csdn.net/dz4543/article/details/80699431

    https://docs.opencv.org/master/dd/d1a/group__imgproc__feature.html#ga47849c3be0d0406ad3ca45db65a25d2d

    https://blog.csdn.net/on2way/article/details/47028969

    
    
     
  • 相关阅读:
    Generative Adversarial Nets
    【 剑指Offer 1 】数据结构
    Hopfield神经网络
    LSTMs 长短期记忆网络系列
    【 记忆网络 2 】 End-to-End Memory Network
    MessagePack Java Jackson Dataformat
    MessagePack Java 0.6.X 动态类型
    MessagePack Java 0.6.X 可选字段
    MessagePack Java 0.6.X 不使用注解(annotations)来序列化
    MessagePack Java 0.6.X List, Map 对象的序列化和反序列化
  • 原文地址:https://www.cnblogs.com/mmmmc/p/10518879.html
Copyright © 2011-2022 走看看