標籤:des style http java color 使用
影像處理之霍夫變換(直線檢測演算法)
霍夫變換是映像變換中的經典手段之中的一個,主要用來從映像中分離出具有某種同樣特徵的幾何
形狀(如,直線,圓等)。霍夫變換尋找直線與圓的方法相比與其他方法能夠更好的降低噪
聲幹擾。經典的霍夫變換經常使用來檢測直線,圓,橢圓等。
霍夫變換演算法思想:
以直線檢測為例,每一個像素座標點經過變換都變成都直線特質有貢獻的統一度量,一個簡單
的範例例如以下:一條直線在映像中是一系列離散點的集合,通過一個直線的離散極座標公式,
能夠表達出直線的離散點幾何等式例如以下:
X *cos(theta) + y * sin(theta) = r 當中角度theta指r與X軸之間的夾角,r為到直線幾何垂
直距離。不論什麼在直線上點,x, y都能夠表達,當中 r, theta是常量。該公式圖形表示範範例如以下:
然而在實現的影像處理領域,映像的像素座標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 valueint 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 spaceint 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; } } } } }霍夫變換源圖例如以下:
霍夫變換以後,在霍夫空間顯示範範例如以下:(白色表示已經找到直線訊號)
終於反變換回到像素空間效果例如以下:
一個更好的執行監測直線的結果(輸入為二值映像):
完整的霍夫變換源碼例如以下:
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;}@Overridepublic 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 valueint 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 spaceint 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;}}
轉載文章請務必註明出自本部落格!!