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