图像处理之霍夫变换(直线检测算法)

简介: 图像处理之霍夫变换(直线检测算法)

图像处理之霍夫变换(直线检测算法)


霍夫变换是图像变换中的经典手段之一,主要用来从图像中分离出具有某种相同特征的几何


形状(如,直线,圆等)。霍夫变换寻找直线与圆的方法相比与其它方法可以更好的减少噪


声干扰。经典的霍夫变换常用来检测直线,圆,椭圆等。



霍夫变换算法思想:


以直线检测为例,每个像素坐标点经过变换都变成都直线特质有贡献的统一度量,一个简单


的例子如下:一条直线在图像中是一系列离散点的集合,通过一个直线的离散极坐标公式,


可以表达出直线的离散点几何等式如下:


X *cos(theta) + y * sin(theta)  = r 其中角度theta指r与X轴之间的夹角,r为到直线几何垂


直距离。任何在直线上点,x, y都可以表达,其中 r, theta是常量。该公式图形表示如下:



1341642751_7461.png

然而在实现的图像处理领域,图像的像素坐标P(x, y)是已知的,而r, theta则是我们要寻找


的变量。如果我们能绘制每个(r, theta)值根据像素点坐标P(x, y)值的话,那么就从图像笛卡


尔坐标系统转换到极坐标霍夫空间系统,这种从点到曲线的变换称为直线的霍夫变换。变换


通过量化霍夫参数空间为有限个值间隔等分或者累加格子。当霍夫变换算法开始,每个像素


坐标点P(x, y)被转换到(r, theta)的曲线点上面,累加到对应的格子数据点,当一个波峰出现


时候,说明有直线存在。同样的原理,我们可以用来检测圆,只是对于圆的参数方程变为如


下等式:


(x –a ) ^2 + (y-b) ^ 2 = r^2其中(a, b)为圆的中心点坐标,r圆的半径。这样霍夫的参数空间就


变成一个三维参数空间。给定圆半径转为二维霍夫参数空间,变换相对简单,也比较常用。



编程思路解析:


1.      读取一幅带处理二值图像,最好背景为黑色。


2.      取得源像素数据


3.      根据直线的霍夫变换公式完成霍夫变换,预览霍夫空间结果


4.       寻找最大霍夫值,设置阈值,反变换到图像RGB值空间(程序难点之一)


5.      越界处理,显示霍夫变换处理以后的图像



关键代码解析:


直线的变换角度为[0 ~ PI]之间,设置等份为500为PI/500,同时根据参数直线参数方程的取值


范围为[-r, r]有如下霍夫参数定义:

 // prepare for hough transform
 int centerX = width / 2;
 int centerY = height / 2;
 double hough_interval = PI_VALUE/(double)hough_space;
      
 int max = Math.max(width, height);
 int max_length = (int)(Math.sqrt(2.0D) * max);
 hough_1d = new int[2 * hough_space * max_length];

实现从像素RGB空间到霍夫空间变换的代码为:

// start hough transform now....
int[][] image_2d = convert1Dto2D(inPixels);
for (int row = 0; row < height; row++) {
  for (int col = 0; col < width; col++) {
      int p = image_2d[row][col] & 0xff;
      if(p == 0) continue; // which means background color
      
      // since we does not know the theta angle and r value, 
      // we have to calculate all hough space for each pixel point
      // then we got the max possible theta and r pair.
      // r = x * cos(theta) + y * sin(theta)
      for(int cell=0; cell < hough_space; cell++ ) {
        max = (int)((col - centerX) * Math.cos(cell * hough_interval) + (row - centerY) * Math.sin(cell * hough_interval));
        max += max_length; // start from zero, not (-max_length)
        if (max < 0 || (max >= 2 * max_length)) {// make sure r did not out of scope[0, 2*max_lenght]
                continue;
            }
        hough_2d[cell][max] +=1;
      }
    }
}


寻找最大霍夫值计算霍夫阈值的代码如下:

// find the max hough value
int max_hough = 0;
for(int i=0; i<hough_space; i++) {
  for(int j=0; j<2*max_length; j++) {
    hough_1d[(i + j * hough_space)] = hough_2d[i][j];
    if(hough_2d[i][j] > max_hough) {
      max_hough = hough_2d[i][j];
    }
  }
}
System.out.println("MAX HOUGH VALUE = " + max_hough);
 
// transfer back to image pixels space from hough parameter space
int hough_threshold = (int)(threshold * max_hough);

从霍夫空间反变换回像素数据空间代码如下:

      // transfer back to image pixels space from hough parameter space
      int hough_threshold = (int)(threshold * max_hough);
      for(int row = 0; row < hough_space; row++) {
        for(int col = 0; col < 2*max_length; col++) {
          if(hough_2d[row][col] < hough_threshold) // discard it
            continue;
          int hough_value = hough_2d[row][col];
          boolean isLine = true;
          for(int i=-1; i<2; i++) {
            for(int j=-1; j<2; j++) {
              if(i != 0 || j != 0) {
                      int yf = row + i;
                      int xf = col + j;
                      if(xf < 0) continue;
                      if(xf < 2*max_length) {
                        if (yf < 0) {
                          yf += hough_space;
                        }
                          if (yf >= hough_space) {
                            yf -= hough_space;
                          }
                          if(hough_2d[yf][xf] <= hough_value) {
                            continue;
                          }
                          isLine = false;
                          break;
                      }
              }
            }
          }
          if(!isLine) continue;
          
          // transform back to pixel data now...
              double dy = Math.sin(row * hough_interval);
              double dx = Math.cos(row * hough_interval);
              if ((row <= hough_space / 4) || (row >= 3 * hough_space / 4)) {
                  for (int subrow = 0; subrow < height; ++subrow) {
                    int subcol = (int)((col - max_length - ((subrow - centerY) * dy)) / dx) + centerX;
                    if ((subcol < width) && (subcol >= 0)) {
                      image_2d[subrow][subcol] = -16776961;
                    }
                  }
                } else {
                  for (int subcol = 0; subcol < width; ++subcol) {
                    int subrow = (int)((col - max_length - ((subcol - centerX) * dx)) / dy) + centerY;
                    if ((subrow < height) && (subrow >= 0)) {
                      image_2d[subrow][subcol] = -16776961;
                    }
                  }
                }
        }
      }

霍夫变换源图如下:

1341643177_4426.png

霍夫变换以后,在霍夫空间显示如下:(白色表示已经找到直线信号)


1341643279_6500.png

最终反变换回到像素空间效果如下:

1341643451_7244.png

一个更好的运行监测直线的结果(输入为二值图像):

1341643681_8786.png

完整的霍夫变换源代码如下:

package com.gloomyfish.image.transform;
 
import java.awt.image.BufferedImage;
 
import com.process.blur.study.AbstractBufferedImageOp;
 
public class HoughLineFilter extends AbstractBufferedImageOp {
  public final static double PI_VALUE = Math.PI;
  private int hough_space = 500;
  private int[] hough_1d;
  private int[][] hough_2d;
  private int width;
  private int height;
  
  private float threshold;
  private float scale;
  private float offset;
  
  public HoughLineFilter() {
    // default hough transform parameters
    //  scale = 1.0f;
    //  offset = 0.0f;
    threshold = 0.5f;
    scale = 1.0f;
    offset = 0.0f;
  }
  
  public void setHoughSpace(int space) {
    this.hough_space = space;
  }
  
  public float getThreshold() {
    return threshold;
  }
 
  public void setThreshold(float threshold) {
    this.threshold = threshold;
  }
 
  public float getScale() {
    return scale;
  }
 
  public void setScale(float scale) {
    this.scale = scale;
  }
 
  public float getOffset() {
    return offset;
  }
 
  public void setOffset(float offset) {
    this.offset = offset;
  }
 
  @Override
  public BufferedImage filter(BufferedImage src, BufferedImage dest) {
    width = src.getWidth();
        height = src.getHeight();
 
        if ( dest == null )
            dest = createCompatibleDestImage( src, null );
 
        int[] inPixels = new int[width*height];
        int[] outPixels = new int[width*height];
        getRGB( src, 0, 0, width, height, inPixels );
        houghTransform(inPixels, outPixels);
        setRGB( dest, 0, 0, width, height, outPixels );
        return dest;
  }
 
  private void houghTransform(int[] inPixels, int[] outPixels) {
        // prepare for hough transform
      int centerX = width / 2;
      int centerY = height / 2;
      double hough_interval = PI_VALUE/(double)hough_space;
      
      int max = Math.max(width, height);
      int max_length = (int)(Math.sqrt(2.0D) * max);
      hough_1d = new int[2 * hough_space * max_length];
      
      // define temp hough 2D array and initialize the hough 2D
      hough_2d = new int[hough_space][2*max_length];
      for(int i=0; i<hough_space; i++) {
        for(int j=0; j<2*max_length; j++) {
          hough_2d[i][j] = 0;
        }
      }
      
      // start hough transform now....
      int[][] image_2d = convert1Dto2D(inPixels);
      for (int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            int p = image_2d[row][col] & 0xff;
            if(p == 0) continue; // which means background color
            
            // since we does not know the theta angle and r value, 
            // we have to calculate all hough space for each pixel point
            // then we got the max possible theta and r pair.
            // r = x * cos(theta) + y * sin(theta)
            for(int cell=0; cell < hough_space; cell++ ) {
              max = (int)((col - centerX) * Math.cos(cell * hough_interval) + (row - centerY) * Math.sin(cell * hough_interval));
              max += max_length; // start from zero, not (-max_length)
              if (max < 0 || (max >= 2 * max_length)) {// make sure r did not out of scope[0, 2*max_lenght]
                      continue;
                  }
              hough_2d[cell][max] +=1;
            }
          }
      }
      
    // find the max hough value
    int max_hough = 0;
    for(int i=0; i<hough_space; i++) {
      for(int j=0; j<2*max_length; j++) {
        hough_1d[(i + j * hough_space)] = hough_2d[i][j];
        if(hough_2d[i][j] > max_hough) {
          max_hough = hough_2d[i][j];
        }
      }
    }
    System.out.println("MAX HOUGH VALUE = " + max_hough);
    
    // transfer back to image pixels space from hough parameter space
    int hough_threshold = (int)(threshold * max_hough);
      for(int row = 0; row < hough_space; row++) {
        for(int col = 0; col < 2*max_length; col++) {
          if(hough_2d[row][col] < hough_threshold) // discard it
            continue;
          int hough_value = hough_2d[row][col];
          boolean isLine = true;
          for(int i=-1; i<2; i++) {
            for(int j=-1; j<2; j++) {
              if(i != 0 || j != 0) {
                      int yf = row + i;
                      int xf = col + j;
                      if(xf < 0) continue;
                      if(xf < 2*max_length) {
                        if (yf < 0) {
                          yf += hough_space;
                        }
                          if (yf >= hough_space) {
                            yf -= hough_space;
                          }
                          if(hough_2d[yf][xf] <= hough_value) {
                            continue;
                          }
                          isLine = false;
                          break;
                      }
              }
            }
          }
          if(!isLine) continue;
          
          // transform back to pixel data now...
              double dy = Math.sin(row * hough_interval);
              double dx = Math.cos(row * hough_interval);
              if ((row <= hough_space / 4) || (row >= 3 * hough_space / 4)) {
                  for (int subrow = 0; subrow < height; ++subrow) {
                    int subcol = (int)((col - max_length - ((subrow - centerY) * dy)) / dx) + centerX;
                    if ((subcol < width) && (subcol >= 0)) {
                      image_2d[subrow][subcol] = -16776961;
                    }
                  }
                } else {
                  for (int subcol = 0; subcol < width; ++subcol) {
                    int subrow = (int)((col - max_length - ((subcol - centerX) * dx)) / dy) + centerY;
                    if ((subrow < height) && (subrow >= 0)) {
                      image_2d[subrow][subcol] = -16776961;
                    }
                  }
                }
        }
      }
      
      // convert to hough 1D and return result
      for (int i = 0; i < this.hough_1d.length; i++)
      {
        int value = clamp((int)(scale * this.hough_1d[i] + offset)); // scale always equals 1
        this.hough_1d[i] = (0xFF000000 | value + (value << 16) + (value << 8));
      }
      
      // convert to image 1D and return
      for (int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            outPixels[(col + row * width)] = image_2d[row][col];
          }
      }
  }
  
  public BufferedImage getHoughImage() {
    BufferedImage houghImage = new BufferedImage(hough_2d[0].length, hough_space, BufferedImage.TYPE_4BYTE_ABGR);
    setRGB(houghImage, 0, 0, hough_2d[0].length, hough_space, hough_1d);
    return houghImage;
  }
  
  public static int clamp(int value) {
        if (value < 0)
          value = 0;
        else if (value > 255) {
          value = 255;
        }
        return value;
  }
  
  private int[][] convert1Dto2D(int[] pixels) {
    int[][] image_2d = new int[height][width];
    int index = 0;
    for(int row = 0; row < height; row++) {
      for(int col = 0; col < width; col++) {
        index = row * width + col;
        image_2d[row][col] = pixels[index];
      }
    }
    return image_2d;
  }
 
}

转载文章请务必注明出自本博客!!

学习图像处理,点击视频教程《数字图像处理-基础入门》

相关文章
|
7天前
|
算法 JavaScript 前端开发
在JavaScript中实现基本的碰撞检测算法,我们通常会用到矩形碰撞检测,也就是AABB(Axis-Aligned Bounding Box)碰撞检测
【6月更文挑战第16天】JavaScript中的基本碰撞检测涉及AABB(轴对齐边界框)方法,常用于2D游戏。`Rectangle`类定义了矩形的属性,并包含一个`collidesWith`方法,通过比较边界来检测碰撞。若两矩形无重叠部分,四个条件(关于边界相对位置)均需满足。此基础算法适用于简单场景,复杂情况可能需采用更高级的检测技术或物理引擎库。
43 6
|
15天前
|
算法 计算机视觉
图像处理之积分图应用四(基于局部均值的图像二值化算法)
图像处理之积分图应用四(基于局部均值的图像二值化算法)
22 0
|
15天前
|
监控 算法 图计算
图像处理之积分图应用三(基于NCC快速相似度匹配算法)
图像处理之积分图应用三(基于NCC快速相似度匹配算法)
18 0
|
2天前
|
机器学习/深度学习 算法 语音技术
基于语音信号MFCC特征提取和GRNN神经网络的人员身份检测算法matlab仿真
**语音识别算法概览** MATLAB2022a中实现,结合MFCC与GRNN技术进行说话人身份检测。MFCC利用人耳感知特性提取语音频谱特征,GRNN作为非线性映射工具,擅长序列学习,确保高效识别。预加重、分帧、加窗、FFT、滤波器组、IDCT构成MFCC步骤,GRNN以其快速学习与鲁棒性处理不稳定数据。适用于多种领域。
|
3天前
|
机器学习/深度学习 算法 计算机视觉
基于ADAS的车道线检测算法matlab仿真
**摘要:** 基于ADAS的车道线检测算法利用Hough变换和边缘检测在视频中识别车道线,判断车道弯曲情况,提供行驶方向信息,并高亮显示。在MATLAB2022a中实现,系统包括图像预处理(灰度化、滤波、边缘检测)、车道线特征提取(霍夫变换、曲线拟合)和车道线跟踪,确保在实时场景中的准确性和稳定性。预处理通过灰度转换减少光照影响,滤波去除噪声,Canny算法检测边缘。霍夫变换用于直线检测,曲线拟合适应弯道,跟踪则增强连续帧的车道线检测。
|
10天前
|
机器学习/深度学习 监控 算法
基于yolov2深度学习网络的昆虫检测算法matlab仿真,并输出昆虫数量和大小判决
YOLOv2算法应用于昆虫检测,提供实时高效的方法识别和定位图像中的昆虫,提升检测精度。核心是统一检测网络,预测边界框和类别概率。通过预测框尺寸估算昆虫大小,适用于农业监控、生态研究等领域。在matlab2022A上运行,经过关键升级,如采用更优网络结构和损失函数,保证速度与精度。持续优化可增强对不同昆虫的检测能力。![image.png](https://ucc.alicdn.com/pic/developer-ecology/3tnl7rfrqv6tw_e760ff6682a3420cb4e24d1e48b10a2e.png)
|
3天前
|
机器学习/深度学习 算法 数据可视化
m基于PSO-LSTM粒子群优化长短记忆网络的电力负荷数据预测算法matlab仿真
在MATLAB 2022a中,应用PSO优化的LSTM模型提升了电力负荷预测效果。优化前预测波动大,优化后预测更稳定。PSO借鉴群体智能,寻找LSTM超参数(如学习率、隐藏层大小)的最优组合,以最小化误差。LSTM通过门控机制处理序列数据。代码显示了模型训练、预测及误差可视化过程。经过优化,模型性能得到改善。
18 6
|
1天前
|
缓存 算法
基于机会网络编码(COPE)的卫星网络路由算法matlab仿真
**摘要:** 该程序实现了一个基于机会网络编码(COPE)的卫星网络路由算法,旨在提升无线网络的传输效率和吞吐量。在MATLAB2022a中测试,结果显示了不同数据流个数下的网络吞吐量。算法通过Dijkstra函数寻找路径,计算编码机会(Nab和Nx),并根据编码机会减少传输次数。当有编码机会时,中间节点执行编码和解码操作,优化传输路径。结果以图表形式展示,显示数据流与吞吐量的关系,并保存为`R0.mat`。COPE算法预测和利用编码机会,适应卫星网络的动态特性,提高数据传输的可靠性和效率。
|
3天前
|
算法 调度
基于变异混合蛙跳算法的车间调度最优化matlab仿真,可以任意调整工件数和机器数,输出甘特图
**摘要:** 实现变异混合蛙跳算法的MATLAB2022a版车间调度优化程序,支持动态调整工件和机器数,输出甘特图。核心算法结合SFLA与变异策略,解决Job-Shop Scheduling Problem,最小化总完成时间。SFLA模拟蛙群行为,分组进行局部搜索和全局信息交换。变异策略增强全局探索,避免局部最优。程序初始化随机解,按规则更新,经多次迭代和信息交换后终止。
|
14天前
|
算法
基于GA-PSO遗传粒子群混合优化算法的VRPTW问题求解matlab仿真
摘要: 本文介绍了考虑时间窗的车辆路径问题(VRPTW),在MATLAB2022a中进行测试。VRPTW涉及车辆从配送中心出发,服务客户并返回,需在指定时间窗内完成且满足车辆容量限制,目标是最小化总行驶成本。文章探讨了遗传算法(GA)和粒子群优化(PSO)的基本原理及其在VRPTW中的应用,包括编码、适应度函数、选择、交叉、变异等步骤。同时,提出了动态惯性权重、精英策略、邻域搜索、多种群和启发式信息等优化策略,以应对时间窗限制并提升算法性能。