原始文献:
J.Brian Burns, Allen R.Hanson,Edward M.Riseman, Extracting Straight Lines,IEEE Transactions on Pattern Analysis and Machine Intelligence,(Volume:PAMI-8 , Issue: 4 ),1986
文章下载地址:http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=4767808&tag=1,如果无法下载IEEE的文章就去我的资源里下载:http://download.youkuaiyun.com/detail/ihadl/6446169
作者主页:http://www.ai.sri.com/people/burns/,这里虽然列了这篇文章,但是没有找到源代码,只有一些作者的软件(http://www.ai.sri.com/software_list/),看着都好高端的样子,以后有时间可以下下来看看效果。
在pudn上下到一个该文章的源代码(http://www.pudn.com/downloads204/sourcecode/graph/detail960709.html),不知道是原作者实现的还是后人实现的,总之最后历尽千辛万苦还是可以调试成功的,也是可用的,在此感谢代码实现者!
然后就对该算法进行了研究,对程序进行了调试,前后花费近一个月时间,因此有必要总结一下。
算法的核心步骤:
Introduction中介绍了一些提取线的通用方法和存在的一些问题,其中针对的主要问题是大多数算法利用了像素梯度的大小,而没有利用梯度的方向,因此文章主要利用梯度的方向来提取直线。主要四个步骤:
一、Group pixels into line-support regions based on similarity of gradient orientation
根据梯度的方向构造LSR.主要步骤包括:
(1)选择梯度算子计算x和y方向的梯度,然后计算梯度的方向角度.
(2)利用固定间隔的区间对梯度角度结果进行分割.由于区域增长分割方法产生的偶然错误都会对结果产生巨大影响.因此使用一种固定分区的分割方法.首先将角度360度的角度范围分割为4个区间或者8个区间,然后将具体的角度归并入分好的角度区间,最后再利用邻域搜索的算法对角度区间的结果进行聚合.
(3)利用重叠间隔的区间对梯度角度结果进行分割.由于利用固定的区间会产生问题(比如不同的两条直线由于相邻且角度相近被聚合为一条;一条直线很可能被分到两个不同的LSR当分割区间恰巧将它分开).因此扩展到重叠区间的方法,先以0度为起点,45度的间隔进行一次归并,再以22.5度为起点,45度的间隔进行第二次归并,然后利用获取最长线的原则将两次的结果合并.具体步骤是:1)先获取每个LSR的长度.2)如果某个像素包含在两个不同的LSR中,它就投票给长度长的那个LSR.3)每个LSR都会得到内部像素的投票.4)取投票数在50%以上的作为LSR.大部分的LSR要么获得非常多的票,要么非常少.
二.Approximate the intensity surface by a planar surface
获得LSR以后需要确定准确的直线的位置。第一个先验知识是直线的方向垂直于LSR中各点的梯度方向。第二就是确定沿着这个直线方向的直线的具体位置。利用两平面横切的方法获取具体位置。第一个平面是梯度大小利用最小二乘拟合得到的平面,第二个平面是图像原始灰度值的平均得到的水平平面,这两个平面相交就获得了最终的直线位置。
三.Extract attributes from the line-support region and planar fit.
提取到直线以后可以计算一些线特征,文中提供了一些线特征的计算公式。
四.Filter lines on the attributes to isolate various image eventssuch as long straight lines of any contrast.
根据线特征的一些先验知识剔除不想要的线,留下目标直线。
源代码
具体代码由三个文件组成,首先是bitmap.h和bitmap.c,看代码说明好像是发现了windows.h中的bitmap类有问题,所以就自己重写的bmp的数据结构和读写函数
// bitmap.h
//
// header file for MS bitmap format
//
//
#ifndef BITMAP_H
#define BITMAP_H
unsigned char * read_bmp ( const char * fname , int * width , int * height , int * step );
void write_bmp ( const char *iname , int width , int height , unsigned char *data );
#endif
// bitmap.c
//
// handle MS bitmap I/O. For portability, we don't use the data structure defined in Windows.h
// However, there is some strange thing, the side of our structure is different from what it
// should though we define it in the same way as MS did. So, there is a hack, we use the hardcoded
// constanr, 14, instead of the sizeof to calculate the size of the structure.
// You are not supposed to worry about this part. However, I will appreciate if you find out the
// reason and let me know. Thanks.
//
#include "bitmap.h"
#include <stdio.h >
#include <stdlib.h >
#include <string.h >
#define BMP_BI_RGB 0L
typedef unsigned short BMP_WORD ;
typedef unsigned int BMP_DWORD ;
typedef int BMP_LONG ;
typedef struct {
BMP_WORD bfType ;
BMP_DWORD bfSize ;
BMP_WORD bfReserved1 ;
BMP_WORD bfReserved2 ;
BMP_DWORD bfOffBits ;
} BMP_BITMAPFILEHEADER ;
typedef struct {
BMP_DWORD biSize ;
BMP_LONG biWidth ;
BMP_LONG biHeight ;
BMP_WORD biPlanes ;
BMP_WORD biBitCount ;
BMP_DWORD biCompression ;
BMP_DWORD biSizeImage ;
BMP_LONG biXPelsPerMeter ;
BMP_LONG biYPelsPerMeter ;
BMP_DWORD biClrUsed ;
BMP_DWORD biClrImportant ;
} BMP_BITMAPINFOHEADER ;
BMP_BITMAPFILEHEADER bmfh ;
BMP_BITMAPINFOHEADER bmih ;
unsigned char * read_bmp ( const char *fname , int * width , int * height , int * step ) {
FILE * file =fopen (fname , "rb" );
if (!file ) return NULL ;
// I am doing fread( &bmfh, sizeof(BMP_BITMAPFILEHEADER), 1, file ) in a safe way.
fread ( &(bmfh.bfType ), 2, 1, file );
fread ( &(bmfh.bfSize ), 4, 1, file );
fread ( &(bmfh.bfReserved1 ), 2, 1, file );
fread ( &(bmfh.bfReserved2 ), 2, 1, file );
fread ( &(bmfh.bfOffBits ), 4, 1, file );
BMP_DWORD pos = bmfh.bfOffBits ;
fread ( &bmih , sizeof (BMP_BITMAPINFOHEADER ), 1, file );
// error checking
// "BM" actually
if ( bmfh.bfType != 0x4d42 ) return NULL ;
if ( bmih.biBitCount != 24 ) return NULL ;
fseek ( file , pos , SEEK_SET );
*width = bmih.biWidth ;
*height = bmih.biHeight ;
int padWidth = *width * 3;
int pad = 0;
if ( padWidth % 4 != 0 ) {
pad = 4 - (padWidth % 4) ;
padWidth += pad ;
}
*step = padWidth ;
int bytes = *height * padWidth ;
unsigned char * data = malloc (bytes );
int foo = fread ( data , bytes , 1, file );
if (!foo ) {
free (data );
return NULL ;
}
fclose ( file );
// shuffle bitmap data such that it is (R,G,B) tuples in row-major order
int i , j ;
j = 0;
unsigned char temp ;
unsigned char * in ;
unsigned char * out ;
in = data ;
out = data ;
for ( j = 0; j < *height ; ++j ) {
for ( i = 0; i < *width ; ++i ) {
out [ 1] = in [ 1] ;
temp = in [ 2] ;
out [ 2] = in [ 0] ;
out [ 0] = temp ;
in += 3;
out += 3;
}
in += pad ;
}
return data ;
}
void write_bmp ( const char *iname , int width , int height , unsigned char *data ) {
int bytes , pad ;
bytes = width * 3;
pad = (bytes % 4) ? 4- (bytes % 4) : 0;
bytes += pad ;
bytes *= height ;
bmfh.bfType = 0x4d42; // "BM"
bmfh.bfSize = sizeof (BMP_BITMAPFILEHEADER ) + sizeof (BMP_BITMAPINFOHEADER ) + bytes ;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = /*hack sizeof(BMP_BITMAPFILEHEADER)=14, sizeof doesn't work?*/
14 + sizeof (BMP_BITMAPINFOHEADER );
bmih.biSize = sizeof (BMP_BITMAPINFOHEADER );
bmih.biWidth = width ;
bmih.biHeight = height ;
bmih.biPlanes = 1;
bmih.biBitCount = 24;
bmih.biCompression = BMP_BI_RGB ;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = ( int )( 100 / 2. 54 * 72) ;
bmih.biYPelsPerMeter = ( int )( 100 / 2. 54 * 72) ;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
FILE *foo =fopen (iname , "wb" );
// fwrite(&bmfh, sizeof(BMP_BITMAPFILEHEADER), 1, foo);
fwrite ( &(bmfh.bfType ), 2, 1, foo );
fwrite ( &(bmfh.bfSize ), 4, 1, foo );
fwrite ( &(bmfh.bfReserved1 ), 2, 1, foo );
fwrite ( &(bmfh.bfReserved2 ), 2, 1, foo );
fwrite ( &(bmfh.bfOffBits ), 4, 1, foo );
fwrite (&bmih , sizeof (BMP_BITMAPINFOHEADER ), 1, foo );
bytes / = height ;
unsigned char * scanline = malloc (bytes );
int i , j ;
for (j = 0; j < height ; ++j ) {
memcpy ( scanline , data + j * 3*width , bytes );
for (i = 0; i < width ; ++i ) {
unsigned char temp = scanline [i * 3] ;
scanline [i * 3] = scanline [i * 3+ 2] ;
scanline [i * 3+ 2]