原始文獻:
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.csdn.net/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] = temp;
}
fwrite( scanline, bytes, 1, foo);
}
free(scanline);
fclose(foo);
}
// burns.h
//
#define _BURNS_H
#include <ipp.h>
/**
* Straight line segment.
*/
typedef struct Line {
/** Coordinates of endpoints. */
double x1, y1, x2, y2;
/** Length of line. */
double length;
/** Number of pixels in the line's support. */
unsigned int num_pixels;
/** Number of pixels who voted for line. */
unsigned int num_votes;
/** Slope in the x-direction. */
double tangent_x;
/** Slope in the y-direction. */
double tangent_y;
/** Midpoint in the x-direction. */
double midpoint_x;
/** Midpoint in the y-direction. */
double midpoint_y;
} Line;
void print_lines(Line* line, int num_lines);
void free_lines(Line* line);
/**
* Find straight line segments in a grayscale image.
* @Article{ Burns1,
* title = "Extracting Straight Lines",
* author = "J. B. Burns and A. R. Hanson and E. M. Riseman",
* journal = "IEEE Transactions on Pattern Analysis and Machine Intelligence",
* pages = "425--455",
* month = jul,
* year = "1986"
* }
*
* @param pixels Array of bytes, the image data.
* @param step Interval in bytes between consecutive rows in the image.
* @param width Width of region of interest.
* @param height Height of region of interest.
* @param roi Region of interest, the size of the image.
* @param num_buckets Increasing the number of buckets decreases the tolerance
* for variations in the gradient orientation along a line. Recommended
* value: 8.
* @param min_gradient Treat gradients below this magnitude (taxi cab distance [0..255])
* as zero. Increasing this value eliminates lines with fuzzy boundaries.
* @param min_length Do not return lines below this length.
*/
void burns_line_extraction(Ipp8u *image, int step, int width, int height,
int num_buckets, int min_gradient, double min_length,
Line** lines, int* num_lines);
void burns_line_extraction_demo(Line** lines, int* num_lines);
#endif /* _BURNS_H */
#ifndef _BURNS_H
#define _BURNS_H
#include <ipp.h>
/**
* Straight line segment.
*/
typedef struct Line {
/** Coordinates of endpoints. */
double x1, y1, x2, y2;
/** Length of line. */
double length;
/** Number of pixels in the line's support. */
unsigned int num_pixels;
/** Number of pixels who voted for line. */
unsigned int num_votes;
/** Slope in the x-direction. */
double tangent_x;
/** Slope in the y-direction. */
double tangent_y;
/** Midpoint in the x-direction. */
double midpoint_x;
/** Midpoint in the y-direction. */
double midpoint_y;
} Line;
void print_lines(Line* line, int num_lines);
void free_lines(Line* line);
/**
* Find straight line segments in a grayscale image.
* @Article{ Burns1,
* title = "Extracting Straight Lines",
* author = "J. B. Burns and A. R. Hanson and E. M. Riseman",
* journal = "IEEE Transactions on Pattern Analysis and Machine Intelligence",
* pages = "425--455",
* month = jul,
* year = "1986"
* }
*
* @param pixels Array of bytes, the image data.
* @param step Interval in bytes between consecutive rows in the image.
* @param width Width of region of interest.
* @param height Height of region of interest.
* @param roi Region of interest, the size of the image.
* @param num_buckets Increasing the number of buckets decreases the tolerance
* for variations in the gradient orientation along a line. Recommended
* value: 8.
* @param min_gradient Treat gradients below this magnitude (taxi cab distance [0..255])
* as zero. Increasing this value eliminates lines with fuzzy boundaries.
* @param min_length Do not return lines below this length.
*/
void burns_line_extraction(Ipp8u *image, int step, int width, int height,
int num_buckets, int min_gradient, double min_length,
Line** lines, int* num_lines);
void burns_line_extraction_demo(Line** lines, int* num_lines);
#endif /* _BURNS_H */
// burns.c
//
#include "burns.h"
#include "bitmap.h"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/gl.h>
/**
* Convenient way to group image parameters which tend to go together.
*/
typedef struct Image8u {
/** The actual pixel data of the image. */
Ipp8u* pixels;
/**
* Interval in bytes between consecutive rows. This may be different
* than the image width because ippiMalloc adds padding for better
* alignment.
*/
int step;
/**
* Region of interest (image size).
*/
IppiSize roi;
} Image8u;
/**
* Allocate a new image.
*/
static Image8u new_Image8u(int width, int height) {
int step;
Ipp8u* pixels = ippiMalloc_8u_C1(width, height, &step);
IppiSize roi = {width, height};
Image8u out = {pixels, step, roi};
return out;
}
/**
* Free memory associated with an image.
*/
static void free_Image8u(Image8u image) {
ippiFree(image.pixels);
}
/**
* Convenient way to group image parameters which tend to go together.
*/
typedef struct Image16s {
/** The actual pixel data of the image. */
Ipp16s* pixels;
/**
* Interval in pixels between consecutive rows. Multiply this by
* sizeof(Ipp16s) before using in ippi functions.
*/
int step;
IppiSize roi;
} Image16s;
/**
* Allocate a new image.
*/
static Image16s new_Image16s(int width, int height) {
int step;
Ipp16s* pixels = ippiMalloc_16s_C1(width, height, &step);
IppiSize roi = {width, height};
Image16s out = {pixels, step/sizeof(Ipp16s), roi};
return out;
}
/**
* Free memory associated with an image.
*/
static void free_Image16s(Image16s image) {
ippiFree(image.pixels);
}
/**
* Allocate a new Line object.
*/
static Line* new_Line() {
Line* out = malloc(sizeof(Line));
out->num_pixels = 0;
out->num_votes = 0;
}
void print_lines(Line* lines, int num_lines) {
int i;
printf("%d Lines:\n", num_lines);
for (i = 0; i < num_lines; ++i) {
Line* line = &lines[i];
printf("Line from (%f %f) to (%f %f)\n",
line->x1, line->y1,
line->x2, line->y2);
}
}
void free_lines(Line* lines) {
free(lines);
}
/**
* Part of a connected component of an image. Connected components are
* represented by tree loops (i.e. n-ary trees where the root points to itself)
* where each node is a Region.
*/
typedef struct Region {
/** Pointer to the parent region. */
struct Region* parent;
/**
* Rank is a measure of the complexity of a connected component.
* It is used when merging two connected components to decide which
* will be the root node.
*/
int rank;
/** Line associated with the region. */
Line* line;
/**
* Regions are allocated in a linked list for easy iteration, this
* points to the next region in the list.
*/
struct Region* next;
} Region;
/**
* Allocate a new region / connected component.
*/
static Region* new_Region() {
Region* out = malloc(sizeof(Region));
out->parent = out;
out->rank = 0;
out->line = NULL;
out->next = NULL;
return out;
}
/**
* Find the root Region of a connected component. As a side-effect this updates
* the tree so that all the Regions on the path to the root point directly to
* the root, speeding up future lookups.
*/
static Region* region_root(Region* c) {
if (c->parent == c) return c;
return c->parent = region_root(c->parent);
}
/**
* Merge two connected components by linking one's root to the other. The
* smaller component is always linked to the larger one, to avoid
* badly unbalanced trees.
*/
static void union_regions(Region* a, Region* b) {
if (a == b) return;
Region* aroot = region_root(a);
Region* broot = region_root(b);
if (aroot->rank > broot->rank) {
broot->parent = aroot;
} else if (aroot->rank < broot->rank) {
aroot->parent = broot;
} else if (aroot != broot) {
broot->parent = aroot;
++aroot->rank;
}
}
/**
* Map of pixels to regions. This mainly exists for compatibility with the AT
* macro.
*/
typedef struct ImageR {
/** Array of pointers to regions. */
Region** pixels;
/** Interval (in units of sizeof(void*)) between consecutive rows. */
int step;
/** Region of interest; size of the image. */
IppiSize roi;
} ImageR;
/**
* Allocate a new region map.
*/
static ImageR new_ImageR(int width, int height) {
IppiSize roi = {width, height};
ImageR out = {calloc(width*height, sizeof(Region*)), width, roi};
return out;
}
/**
* Free data associated with a region map.
*/
static void free_ImageR(ImageR image) {
free(image.pixels);
}
/**
* Return the element at coordinates (x,y) as measured from the top left in an
* Image8u-like struct.
*/
#define AT(image, x, y) (image).pixels[(y)*(image).step + (x)]
/**
* Print the contents of a region map for debugging purposes.
*/
static void print_ImageR(ImageR image) {
int x, y;
for (y = 0; y < image.roi.height; ++y) {
for (x = 0; x < image.roi.width; ++x) {
printf("%p ", AT(image, x, y));
}
printf("\n");
}
}
/**
* Print the contents of a grayscale image for debugging purposes.
*/
static void print_Image8u(Image8u image) {
int x, y;
for (y = 0; y < image.roi.height; ++y) {
for (x = 0; x < image.roi.width; ++x) {
printf("%d", AT(image, x, y)/26);
}
printf("\n");
}
}
/**
* Calculate 4-connected components of the image based on gradient orientation.
* See http://people.csail.mit.edu/rahimi/connected/ for more details.
*/
static ImageR line_support(Image8u gradient_m, Image8u gradient_q, Region** rtail) {
int x, y;
ImageR out = new_ImageR(gradient_m.roi.width, gradient_m.roi.height);
Region* rhead = *rtail;
// first pixel
if (AT(gradient_m, 0, 0)) {
AT(out, 0, 0) = (*rtail) = (*rtail)->next = new_Region();
}
// first row
for (x = 1; x < gradient_m.roi.width; ++x) {
if (AT(gradient_m, x, 0)) {
if (AT(out, x-1, 0) && AT(gradient_q,x,0) == AT(gradient_q,x-1,0)) {
// matches left pixel
AT(out, x, 0) = AT(out, x-1, 0);
} else {
AT(out, x, 0) = (*rtail) = (*rtail)->next = new_Region();
}
}
}
// remaining rows
for (y = 1; y < gradient_m.roi.height; ++y) {
// first pixel of row
if (AT(gradient_m, 0, y)) {
if (AT(out, 0, y-1) && AT(gradient_q,0,y) == AT(gradient_q,0,y-1)) {
// matches top pixel
AT(out, 0, y) = AT(out, 0, y-1);
} else {
AT(out, 0, y) = (*rtail) = (*rtail)->next = new_Region();
}
}
// remaining pixels of row
for (x = 1; x < gradient_m.roi.width; ++x) {
if (AT(gradient_m, x, y)) {
if (AT(out, x-1, y) && AT(gradient_q,x,y) == AT(gradient_q,x-1,y)) {
// matches left pixel
AT(out, x, y) = AT(out, x-1, y);
if (AT(out, x, y-1) && AT(gradient_q,x,y) == AT(gradient_q,x,y-1)) {
// matches top and left pixels
union_regions(AT(out, x, y), AT(out, x, y-1));
AT(out, x, y) = AT(out, x, y-1);
}
} else if (AT(out, x, y-1) && AT(gradient_q,x,y-1) == AT(gradient_q,x,y)) {
// matches top pixel
AT(out, x, y) = AT(out, x, y-1);
} else {
// matches neither top nor left pixels
AT(out, x, y) = (*rtail) = (*rtail)->next = new_Region();
}
}
}
}
// Merge regions and compute dimensions of each region
for (y = 0; y < gradient_m.roi.height; ++y) {
for (x = 0; x < gradient_m.roi.width; ++x) {
if (!AT(out, x, y)) continue;
// Find the root for this connected component.
AT(out, x, y) = region_root(AT(out, x, y));
Region* region = AT(out, x, y);
if (!region->line) {
region->line = new_Line();
region->line->x1 = region->line->x2 = x+1;
region->line->y1 = region->line->y2 = y+1;
} else {
if (x+1> region->line->x2) region->line->x2 = x+1;
if (x+1< region->line->x1) region->line->x1 = x+1;
region->line->y2 = y+1;
}
++region->line->num_pixels;
}
}
// remove temporary regions from the linked list
Region* rtmp;
while (rhead->next != NULL) {
if (!rhead->next->line) {
rtmp = rhead->next->next;
free(rhead->next);
rhead->next = rtmp;
} else {
rhead = rhead->next;
}
}
*rtail = rhead;
return out;
}
static void estimate_line_properties(ImageR regions, Image8u gradient_m, Image16s gradient_x, Image16s gradient_y) {
int x, y;
// calculate correct line parameters for good lines
for (y = 0; y < regions.roi.height; ++y) {
for (x = 0; x < regions.roi.width; ++x) {
Region* region = AT(regions, x, y);
if (!region) continue;
Line* line = region->line;
if (!line) continue;
int new_num_pixels = line->num_pixels + 1;
double weight = hypot(line->tangent_x, line->tangent_y) * line->num_pixels;
double sx = AT(gradient_x, x, y);
double sy = AT(gradient_y, x, y);
line->tangent_x = line->tangent_x * line->num_pixels / (double)new_num_pixels + sx / new_num_pixels;
line->tangent_y = line->tangent_y * line->num_pixels / (double)new_num_pixels + sy / new_num_pixels;
double new_weight = hypot(line->tangent_x, line->tangent_y) * new_num_pixels;
double current_weight = hypot(sx, sy);
line->midpoint_x = (line->midpoint_x * weight + (x+1) * current_weight) / (weight + current_weight);
line->midpoint_y = (line->midpoint_y * weight + (y+1) * current_weight) / (weight + current_weight);
line->num_pixels = new_num_pixels;
}
}
}
static void draw_Image8u(Image8u image) {
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1,0,0);
glLineWidth(2);
glRasterPos2f(0,0);
glDrawPixels(image.roi.width, image.roi.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, image.pixels);
}
static int min(int a, int b) {
if (a <= b) return a;
else return b;
}
static int max(int a, int b) {
if (a >= b) return a;
else return b;
}
void burns_line_extraction(Ipp8u* pixels, int step, int width, int height,
int num_buckets, int min_gradient, double min_length,
Line** lines, int* num_lines)
{
IppiSize roi = {width, height};
Image8u image = {pixels, step, roi};
int x, y;
// The edges of the image do not have a well-defined gradient (using
// the Sobel filter), so we will ignore them for the purposes of this
// algorithm.
roi.height -= 2;
roi.width -= 2;
Image16s gradient_x = new_Image16s(roi.width, roi.height);
ippiFilterSobelHoriz_8u16s_C1R(&AT(image, 1, 1), image.step, gradient_x.pixels, gradient_x.step*sizeof(Ipp16s), gradient_x.roi, ippMskSize3x3);
Image16s gradient_y = new_Image16s(roi.width, roi.height);
ippiFilterSobelVert_8u16s_C1R(&AT(image, 1, 1), image.step, gradient_y.pixels, gradient_y.step*sizeof(Ipp16s), gradient_y.roi, ippMskSize3x3);
// Gradient magnitude and direction (in integer multiples of pi/8).
// We could do this with ippiFilter but since we have to iterate
// through the image anyways, doing the filtering at the same time
// avoids unnecessary memory allocation.
Image8u gradient_m = new_Image8u(roi.width, roi.height);
// Gradient direction quantized into buckets starting at 0 degrees.
Image8u gradient_q1 = new_Image8u(roi.width, roi.height);
// Gradient direction quantized into buckets starting at 180/num_buckets
// degrees (i.e. offset by half a bucket from gradient_q1).
Image8u gradient_q2 = new_Image8u(roi.width, roi.height);
for (y = 0; y < gradient_m.roi.height; ++y) {
for (x = 0; x < gradient_m.roi.width; ++x) {
int hgrad = AT(gradient_x, x, y);
int vgrad = AT(gradient_y, x, y);
int gmag = abs(hgrad)/4+ abs(vgrad)/4;
if (gmag > min_gradient) {
double theta = atan2((double)vgrad, (double)hgrad);
int bucket = (int)floor((theta+M_PI) * (double)num_buckets/M_PI);
AT(gradient_m, x, y) = gmag;
AT(gradient_q1, x, y) = bucket/2;
AT(gradient_q2, x, y) = ((bucket+1)%(2*num_buckets))/2;
} else {
AT(gradient_m, x, y) = 0;
AT(gradient_q1, x, y) = 0;
AT(gradient_q2, x, y) = 0;
}
}
}
// Find the line-support regions of the image; regions will be
// collected in a linked list so we can iterate through them later.
Region* rhead = new_Region();
Region* rtail = rhead;
ImageR regions1 = line_support(gradient_m, gradient_q1, &rtail);
ImageR regions2 = line_support(gradient_m, gradient_q2, &rtail);
// free temporary list head
rtail = rhead;
rhead = rhead->next;
free(rtail);
// gradient orientations no longer needed
free_Image8u(gradient_q1);
free_Image8u(gradient_q2);
// approximate length of each region's line
for (rtail = rhead; rtail != NULL; rtail = rtail->next) {
Line* line = rtail->line;
line->length = abs(line->x1 - line->x2)/2+ abs(line->y1 - line->y2)/2;
}
// vote on regions
for (y = 0; y < regions1.roi.height; ++y) {
for (x = 0; x < regions1.roi.width; ++x) {
if (!AT(regions1, x, y)) continue;
Region* c1 = AT(regions1, x, y);
Region* c2 = AT(regions2, x, y);
if (c1->line->length >= c2->line->length) {
++c1->line->num_votes;
} else {
++c2->line->num_votes;
}
}
}
// discard bad (undervoted) lines
for (rtail = rhead; rtail != NULL; rtail = rtail->next) {
Line* line = rtail->line;
if (line) {
if (line->length < min_length || line->num_votes < line->num_pixels/2) {
free(line);
rtail->line = NULL;
} else {
// reset pixel counter for averages
line->num_pixels = 0;
}
}
}
estimate_line_properties(regions1, gradient_m, gradient_x, gradient_y);
estimate_line_properties(regions2, gradient_m, gradient_x, gradient_y);
// calculate more accurate endpoints for lines
for (rtail = rhead; rtail != NULL; rtail = rtail->next) {
Line* line = rtail->line;
if (line) {
double xmin = line->x1; double xmax = line->x2;
double ymin = line->y1; double ymax = line->y2;
line->x1 = line->midpoint_x - (line->midpoint_y - ymin)*line->tangent_x/line->tangent_y;
if (line->x1 < xmin) {
line->x1 = xmin;
line->y1 = line->midpoint_y - (line->midpoint_x - xmin)*line->tangent_y/line->tangent_x;
} else if (line->x1 > xmax) {
line->x1 = xmax;
line->y1 = line->midpoint_y + (xmax - line->midpoint_x)*line->tangent_y/line->tangent_x;
}
line->x2 = line->midpoint_x + (ymax - line->midpoint_y)*line->tangent_x/line->tangent_y;
if (line->x2 < xmin) {
line->x2 = xmin;
line->y2 = line->midpoint_y - (line->midpoint_x - xmin)*line->tangent_y/line->tangent_x;
} else if (line->x2 > xmax) {
line->x2 = xmax;
line->y2 = line->midpoint_y + (xmax - line->midpoint_x)*line->tangent_y/line->tangent_x;
}
// re-calculate length
line->length = hypot(line->x1 - line->x2, line->y1 - line->y2);
// discard too-short lines
if (line->length < min_length) {
free(line);
rtail->line = NULL;
}
}
}
// count lines
*num_lines = 0;
for (rtail = rhead; rtail != NULL; rtail = rtail->next) {
Line* line = rtail->line;
if (line) ++(*num_lines);
}
// gradient information no longer needed
free_Image8u(gradient_m);
free_Image16s(gradient_y);
free_Image16s(gradient_x);
// region maps no longer needed
free_ImageR(regions1);
free_ImageR(regions2);
// Lines will be collected in an array
*lines = malloc(*num_lines * sizeof(Line));
int i = 0;
// build output list and free regions
while (rhead != NULL) {
Line* line = rhead->line;
if (line) {
// found the root of a connected component with a good line
(*lines)[i++] = *line;
free(line);
}
// free the region and move to the next one
rtail = rhead;
rhead = rhead->next;
free(rtail);
}
}
void burns_line_extraction_demo(Line** lines, int* num_lines) {
IppiSize roi;
int rgb_step, gray_step;
Ipp8u* rgb_pixels = read_bmp("demo.bmp", &roi.width, &roi.height, &rgb_step);
Ipp8u* gray_pixels = ippiMalloc_8u_C1(roi.width, roi.height, &gray_step);
ippiRGBToGray_8u_C3C1R(rgb_pixels, rgb_step, gray_pixels, gray_step, roi);
burns_line_extraction(gray_pixels, gray_step, roi.width, roi.height, 8, 50, 10, lines, num_lines);
}
#include "bitmap.h"
#include <stdio.h>
#include <math.h>
#include <ippcc.h>
#include <GL/gl.h>
#include <GL/glut.h>
Ipp8u* rgb_pixels;
int rgb_step;
IppiSize rgb_roi;
Ipp8u* gray_pixels;
int gray_step;
IppiSize gray_roi;
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2);
glRasterPos2f(0,0);
glDrawPixels(gray_roi.width, gray_roi.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, gray_pixels);
Line* lines;
int num_lines;
int i;
burns_line_extraction(gray_pixels, gray_step, gray_roi.width, gray_roi.height,
8, 10, 5,
&lines, &num_lines);
glColor3f(1,0,0);
glBegin(GL_LINES);
for (i = 0; i < num_lines; ++i) {
Line* line = &lines[i];
glVertex3d(line->x1, line->y1, 0);
glVertex3d(line->x2, line->y2, 0);
}
glEnd();
glPointSize(3);
glColor3f(0,0,1);
glBegin(GL_POINTS);
for (i = 0; i < num_lines; ++i) {
Line* line = &lines[i];
glVertex3d(line->midpoint_x, line->midpoint_y, 0);
}
glEnd();
glFlush();
free_lines(lines);
}
int main(int argc, char **argv) {
IppiSize rgb_roi;
rgb_pixels = read_bmp("demo.bmp", &rgb_roi.width, &rgb_roi.height, &rgb_step);
gray_pixels = ippiMalloc_8u_C1(rgb_roi.width, rgb_roi.height, &gray_step);
gray_roi = rgb_roi;
ippiRGBToGray_8u_C3C1R(rgb_pixels, rgb_step, gray_pixels, gray_step, rgb_roi);
glutInit(&argc, argv);
glutInitWindowSize(gray_roi.width, gray_roi.height);
glutInitWindowPosition(0, 0);
glutInitDisplayMode(GLUT_RGB);
glutCreateWindow("window");
glClearColor(0.0, 0.0, 0.0, 0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, gray_roi.width, 0, gray_roi.height, -1, 1);
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
編譯過程中可能遇到的問題
理論上這幾個c文件利用gcc是可以直接編譯通過的,但無奈本人的gcc功力不夠,最後只能把它移植到VC6中進行編譯和調試。下面列 一下用VC6編譯過程中可能遇到的問題和解決方案:
1、在自定義的min和max函數報錯:錯誤:expected identifier or ‘(’ before ‘int’,找了半天感覺應該是max或者min這個函數名稱已經被用過了?改成Mymax或者Mymin以後就行了
2、結構體初始化報錯,比如Image8u out = {pixels, step, roi};改成各個成員分別初始化就行了:
Image8u out;
out.pixels = pixels;
out.step = step;
out.roi = roi;
3、OpenGL環境的配置可以參考我之前寫過的博客Windows下配置OpenGL的開發環境,以VC6爲例
需要的資源下載
最後列一下編譯需要的資源,其實主要是ipp庫和OpenGL
OpenGL上面說過了,參考我之前發過的博客,裏面有資源下載地址,找個簡單例子測試下沒問題就行了。
ipp庫就比較噁心了,我是在intel的網站下載了個試用版http://software.intel.com/en-us/intel-ipp
反正有頭文件和lib就行了。
最後貼幾張效果圖吧,費這麼大勁總得搞點東西出來啊。
需要注意的是不知爲啥最後只能顯示寬度爲64倍數的bmp圖片(其他寬度的圖片提取結果應該是沒問題,只是顯示一團糟,懷疑是bitmap的數據結構導致的,高手可以改進下,爭取去掉它的ipp數據結構,換成opencv或者gdal的數據結構,這樣就啥圖片都可以處理了)
首先是經典的lena中的直線
再來看一張高分辨率遙感數據的直線提取結果