hough变换检测直线

Posted 萌虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hough变换检测直线相关的知识,希望对你有一定的参考价值。

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变换检测直线的主要内容,如果未能解决你的问题,请参考以下文章

hough变换检测直线

Hough变换原始形式-直线检测

Hough变换原始形式-直线检测

matlab 霍夫变换(hough) 检测直线

MATLAB中用霍夫变换检测出了直线之后怎么求斜率

图像中的线检测——hough变换