hough變換檢測直線Java


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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM