霍夫直线变换 python版

霍夫直线变换 python版

霍夫变换是一种常用的在图像中查找直线的方法(当前也被推广到查找圆等其他几何形状)。

其基本原理是将原图像空间上的每个点变为新空间上的一条曲线,然后在新空间上检测多条曲线的焦点从而得到原图像空间上的直线方程。

本文主要以直线为例介绍霍夫直线变换的细节和原理。

霍夫直线变换

图像空间向 Hesse 仿射空间变换

对于霍夫直线变换首先需要明确的是变换之后的空间并不是传统意义上的极坐标系,而是 Hesse 仿射坐标系

对于图像空间上的一个点 (x_0,y_0) 而言,对应 Hesse 仿射坐标系下的曲线方程为 r=x_0cos\theta+y_0sin\theta

具体曲线绘制方法就是遍历 \theta 空间,根据 \theta 求出 r。

    def get_hough_space(self, edge):
        self.edge = edge
        self.rmax = int(math.hypot(edge.shape[0], edge.shape[1]))  # the maximum value rho can get.
        self.hough_space = np.zeros((len(self.thetas), 2 * self.rmax + 1),
                                    dtype=np.uint8)  # This is the hough space that we will vote.

        h, w = self.edge.shape
        for x in range(w): # the X and Y coordinates in an image is different thats why x == img.shape[1]
            for y in range(h):
                if self.edge[y, x] != 0:
                    for i, theta in enumerate(self.thetas): # rotate the line
                        th = math.radians(theta)
                        ro = round(x * math.cos(th) + y * math.sin(th)) + self.rmax # we add r_max to get rid of negative values for indexing.
                        if ro <= 2 * self.rmax:
                            self.hough_space[i, ro] += 1 # vote
        return self.hough_space

这样原始图像中一个点(一般输入提取得到的图像边缘信息)就转换为了 Hesse 仿射坐标系下的一条曲线。而 Hesse 仿射坐标系曲线上的每个点都表示了原始图像空间上经过转换点的一条直线。

因此可以通过分析 Hesse 仿射坐标系下曲线的相交情况对原始图像空间中的直线进行检测。

Hesse 仿射空间向图像空间变换

在检测到 Hesse 仿射坐标系下的若干峰值后,根据之前的分析每一个峰值都对应了原始图像空间上的一条直线。比如某一峰值 (r_0,\theta_0),图像空间对应直线方程为 cos\theta_0x+sin\theta_0y=r_0

这里的 r_0 的绝对值就是原始图像坐标系下坐标原点到直线的垂直距离;\theta_0 为图像空间上直线与 y 轴的夹角。在图像坐标系 x 轴正向水平向右,y轴正向竖直向下定义下,\theta_0 =0 表示与 y 轴平行,顺时针旋转为正,逆时针旋转为负,取值范围为 [-90, 90) 。

对于所求的方程 cos\theta_0x+sin\theta_0y=r_0,可以发现这个直线一定通过图像空间上的 (cos\theta_0r_0,sin\theta_0r_0) 点,具体推导可以代入公式利用 sin\theta^2+cos\theta^2=1 得到。同时由于直线法向量为 (cos\theta_0,sin\theta_0),因此该直线可以很简单的确定。

    def get_lines(self, hough_space):
        self.lines = dict()

        # modify hough_space by yourself, then input
        y, x = np.where(hough_space)
        for i, j in zip(y, x):
            th = math.radians(self.thetas[i])
            ro = j - self.rmax  # we extract rmax since we added it on the preprocessing phase.
            a = math.cos(th)
            b = math.sin(th)
            x0 = a * ro
            y0 = b * ro
            x1 = int(round(x0 + self.rmax * (-b)))
            y1 = int(round(y0 + self.rmax * (a)))
            x2 = int(round(x0 - self.rmax * (-b)))
            y2 = int(round(y0 - self.rmax * (a)))
            if i in self.lines:
                self.lines[i].append(((x1, y1), (x2, y2)))
            else:
                self.lines[i] = [((x1, y1), (x2, y2))]

        return self.lines

完整范例程序

最后给出完整的程序,程序参考了 GitHub - sercanamac/Line-Detection-Using-Hough-Space

需要特别说明的是绘制的 hough space 图的横向为 r,纵向为 \theta而且纵坐标数值并不是实际的 \theta,实际的 \theta 需要通过纵坐标数值代入 self.thetas 数组得到;同时为了避免 r 向上存在负值,转换时 r 上统一叠加了 self.rmax。

#!usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import math
import numpy as np
import matplotlib.pyplot as plt
import copy


class HoughTransformer(object):
    def __init__(self, theta_step=1):
        self.thetas = [i for i in range(-90, 90, theta_step)]

    def get_hough_space(self, edge):
        self.edge = edge
        self.rmax = int(math.hypot(edge.shape[0], edge.shape[1]))  # the maximum value rho can get.
        self.hough_space = np.zeros((len(self.thetas), 2 * self.rmax + 1),
                                    dtype=np.uint8)  # This is the hough space that we will vote.

        h, w = self.edge.shape
        for x in range(w): # the X and Y coordinates in an image is different thats why x == img.shape[1]
            for y in range(h):
                if self.edge[y, x] != 0:
                    for i, theta in enumerate(self.thetas): # rotate the line
                        th = math.radians(theta)
                        ro = round(x * math.cos(th) + y * math.sin(th)) + self.rmax # we add r_max to get rid of negative values for indexing.
                        if ro <= 2 * self.rmax:
                            self.hough_space[i, ro] += 1 # vote
        return self.hough_space

    def topk(self, hough_space, k):
        idx = np.argpartition(hough_space.ravel(), hough_space.size - k)[-k:]
        return np.column_stack(np.unravel_index(idx, hough_space.shape))

    def get_lines(self, hough_space):
        self.lines = dict()

        # modify hough_space by yourself, then input
        y, x = np.where(hough_space)
        for i, j in zip(y, x):
            th = math.radians(self.thetas[i])
            ro = j - self.rmax  # we extract rmax since we added it on the preprocessing phase.
            a = math.cos(th)
            b = math.sin(th)
            # line equation: ax + by = r            
            x0 = a * ro
            y0 = b * ro
            x1 = int(round(x0 + self.rmax * (-b)))
            y1 = int(round(y0 + self.rmax * (a)))
            x2 = int(round(x0 - self.rmax * (-b)))
            y2 = int(round(y0 - self.rmax * (a)))
            if i in self.lines:
                self.lines[i].append(((x1, y1), (x2, y2)))
            else:
                self.lines[i] = [((x1, y1), (x2, y2))]

        return self.lines

    def draw_lines(self, edge, lines):
        img = copy.deepcopy(edge)
        for degree, points in lines.items():
            for p1, p2 in points:
                cv2.line(img, p1, p2, 255, 1)
        plt.imshow(img)
        plt.show()


def main():
    edge = np.zeros((600, 600), dtype=np.uint8)
    cv2.line(edge, (0, 0), (200, 400), 255, 1)
    plt.imshow(edge)
    plt.show()
    ht = HoughTransformer()
    hough_space = ht.get_hough_space(edge)
    plt.imshow(hough_space)
    plt.show()
    peak = ht.topk(hough_space, 1)
    print("peak: {}(theta),{}(r)".format(ht.thetas[peak[0][0]], peak[0][1]-ht.rmax))
    hough_space[hough_space < hough_space[peak[0][0], peak[0][1]]] = 0
    lines = ht.get_lines(hough_space)
    ht.draw_lines(edge, lines)


if __name__ == '__main__':
    main()

最后说明

最后需要说明的是霍夫直线变换只能得到直线,而不能得到线段,即无法获取图像中线段的端点信息。

Opencv 中提供了 cv2.HoughLines 利用霍夫变换进行直线检测,而且还提供了能够检测线段的 cv2.HoughLinesP。

cv2.HoughLinesP 介绍

def HoughLinesP(image, rho, theta, threshold, lines=..., minLineLength=..., maxLineGap=...)

输入参数:

  1. image:待检测直线的二值图像
  2. rho:坐标原点到直线距离的检测精度
  3. theta:直线与 y 轴夹角的检测精度
  4. threshold:判定存在直线的阈值(直线通过的点数)
  5. lines:输出的直线段(以直线段两点表示)
  6. minLineLength:最后输出的直线段可能有多段更小的直线段拼接而成,minLineLength 限制了拼接的直线段的最小长度
  7. maxLineGap:限制了多条直线段能够进行拼接的最小间隔

其检测逻辑:

  1. 基于 threshold 检测图像中的直线段
  2. 根据 maxLineGap 组合直线
  3. 根据 minLineLenght 输出满足要求的拼接后的直线段
  • 2
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值