1. 程式人生 > >hough變換檢測直線

hough變換檢測直線

需要 lac 去掉 坐標軸 不能 blog .com 最大的 png

hough變換檢測直線原理:

假設在圖像中存在一條直線y=k*x+b(此時k,b未知)。取直線上的任意兩點進行說明,設為(x0,y0),(x1,y1)。

所有經過點(x0,y0)的直線滿足:-x0*k+y0=b ---式1,那麽以k、b為直角坐標軸做式1對應直線;

所有經過點(x1,y1)的直線滿足:-x1*k+y1=b ---式2,那麽以k、b為直角坐標軸做式2對應直線;

兩直線交於一點(kk,bb),此時該交點對應的直線y=kk*x+bb就是(x0,y0),(x1,y1)所確定的直線。

在hough變換中,我們首先將直線方程轉換為極坐標形式:x*cos(theta)+y*sin(theta)=r ---式3(theta,r未知)。同上述原理,假設已知直線上的點的坐標,那麽經過該點的曲線方程就對應著hough變換空間(即theta,r坐標系)的一條曲線,同一條直線上的點必定在hough變換空間相交於一點(theta*,r*),該點就是待檢測直線方程式3對應的theta、r。當然,在hough變換空間兩條曲線相交確定的(theta,r)並不能足以說明我們檢測到了一條直線,只有當在(theta,r)這一點累加的曲線個數很大,通常是超過了我們提前設定的某個閾值時,我們才認為在(theta,r)是對應有一條直線的,否則我們應該使用極大值抑制的方法將這些幹擾點(theta,r)去掉。下圖是copy過來的,大概看看就行。

技術分享

以下是hough變換檢測直線的封裝類,因實際需求,這裏我是專門用來檢測任意四邊形的四條邊的。

  1 public class LineFilter {
  2     private int w;
  3     private int h;
  4     private int halfX;
  5     private int halfY;
  6     private int rmax;
  7     private int lineNum;
  8     private int[] output;
  9     private int[] n;
 10     private
int[][] acc; 11 private double[] sinValue; 12 private double[] cosValue; 13 private List<Acc> list; 14 15 //Acc累加器,同一個r,theta的點進行累加,累加個數為val 16 public class Acc { 17 private int r = 0; 18 private int theta = 0; 19 private int val = 0; 20 public
Acc() { 21 } 22 } 23 24 public int[] lineDetect(int width, int height, int lineNumber, int[] input) { 25 init(width, height, lineNumber); 26 /* 27 * 轉換:直角坐標空間~極坐標空間 28 * 不斷計算累加,最終得到相同(theta,r)的像素點累加個數acc[theta][r] 29 * */ 30 for (int theta = 0; theta < 180; theta++) { 31 for (int x = 0; x < w; x++) { 32 for (int y = 0; y < h; y++) { 33 if (input[y * w + x] == Color.BLACK) { 34 int r = (int) ((x - halfX) * cosValue[theta] + (y - halfY) * sinValue[theta]); 35 r = r + rmax;//r的原本取值範圍為(-ramx,ramx),加rmax後取值範圍為(0,2ramx) 36 if (r >= 0 && r < 2 * rmax) 37 acc[theta][r]++; 38 } 39 } 40 } 41 } 42 /* 43 * 很重要的一步:在3*3窗口內對累加值進行極大值抑制,保留窗口內累加值最大的(theta,r); 44 * 之後將theta,r,acc[theta][r]添加進list裏面; 45 * 對list進行部分排序; 46 * 之後取出list前面lineNum個Acc對象,通過theta和r值找出直角坐標空間的直線 47 * */ 48 rankList(acc); 49 System.out.println(".........acc個數:" + list.size()); 50 n = new int[lineNum]; 51 for (int i = 0; i < lineNum; i++) { 52 Acc acc = list.get(i); 53 n[i] = drawLine(acc.r, acc.theta, n[i]); 54 System.out.println("檢測出的第" + i + "條直線點的累積個數:" + acc.r + "..." + acc.theta + "..." + acc.val); 55 System.out.println("實際輸出第" + i + "條直線點的個數:" + n[i]); 56 } 57 return output; 58 } 59 60 private void init(int width, int height, int lineNumber) { 61 w = width; 62 h = height; 63 halfX = w / 2; 64 halfY = h / 2; 65 lineNum = lineNumber; 66 output = new int[w * h]; 67 int max = Math.max(w, h); 68 rmax = (int) (Math.sqrt(2.0) * max); 69 acc = new int[180][2 * rmax]; 70 list = new ArrayList<>(); 71 sinValue = new double[180]; 72 cosValue = new double[180]; 73 Arrays.fill(output, Color.WHITE); 74 for (int theta = 0; theta < 180; theta++) { 75 sinValue[theta] = Math.sin((theta * Math.PI) / 180); 76 cosValue[theta] = Math.cos((theta * Math.PI) / 180); 77 } 78 } 79 80 /* 81 * 排序Acc數組,只對前面幾個Acc進行排序,找出lineSize個較大的Acc 82 * */ 83 private void rankList(int[][] acc) { 84 /* 85 * 對(theta,r)進行極大值抑制,因為有時候因為計算誤差或者直線不是很直的原因, 86 * 同一條直線上的點轉換到極坐標空間時,就會出現多對不同的(theta,r),多對不同的(theta,r)轉換到直角坐標空間就出現了多條直線, 87 * 這就是為什麽原本圖像中只有一條直線最後在該位置檢測出了多條直線,因此在進行極坐標到直角坐標轉換之前, 88 * 有必要對(theta,r)進行極大值抑制,只保留累積值val最大的那一對(theta,r) 89 * */ 90 for (int theta = 0; theta < 180; theta++) { 91 for (int r = 0; r < 2 * rmax; r++) { 92 int val = acc[theta][r]; 93 boolean onlyLine = true; 94 if (val > 0) { 95 for (int tt = -1; tt <=1; tt++) { 96 for (int rr = -1; rr <= 1; rr++) { 97 int newtheta = theta + tt; 98 int newr = r + rr; 99 if (newtheta < 0 || newtheta >= 180) 100 newtheta = 0; 101 if (newr < 0 || newr >= 2 * rmax) newr = 0; 102 if (acc[newtheta][newr] > val) onlyLine = false; 103 } 104 } 105 /* 106 *在3*3窗口內累加值最大的(theta,r)我們才添加進list , 107 * 並標記theta,r,以及累加值val 108 * */ 109 if (onlyLine) { 110 Acc subAcc = new Acc(); 111 subAcc.r = r - rmax; 112 subAcc.theta = theta; 113 subAcc.val = acc[theta][r]; 114 list.add(subAcc); 115 } 116 } 117 } 118 } 119 /* 120 * 設置需要檢測的直線條數為lineNum, 121 * 按val值大小升序排列list,當然只需要進行前面部分的排序即可 122 * */ 123 for (int i = 0; i < lineNum; i++) { 124 int max = i; 125 for (int j = i + 1; j < list.size(); j++) { 126 if (list.get(j).val > list.get(max).val) { 127 max = j; 128 } 129 } 130 if (max != i) { 131 Acc accmax = list.get(max); 132 Acc acci = list.get(i); 133 list.set(max, acci); 134 list.set(i, accmax); 135 } 136 } 137 } 138 139 /* 140 *轉換:極坐標空間~直角坐標空間 141 *r=(x-halfx)*cos(theta)+(y-halfy)*sin(theta); 142 * 已知r,theta,x或者y的情況下,通過該式計算出符合條件的y或者x。 143 * 畫出lineNum條直線 144 * */ 145 private int drawLine(int r, int theta, int n) { 146 if (theta >= 45 && theta <= 135) { 147 for (int x = 0; x < w; x++) { 148 int y = (int) ((r - (x - halfX) * cosValue[theta]) / sinValue[theta]) + halfY; 149 if (y >= 0 && y < h) { 150 output[y * w + x] = Color.BLACK; 151 n++; 152 } 153 } 154 } else { 155 for (int y = 0; y < h; y++) { 156 int x = (int) ((r - (y - halfY) * sinValue[theta]) / cosValue[theta]) + halfX; 157 if (x >= 0 && x < w) { 158 output[y * w + x] = Color.BLACK; 159 n++; 160 } 161 } 162 } 163 return n; 164 } 165 166 }

hough變換檢測直線