0%

hough变换

​ hough变换主要用于找线,当然也可以扩展为找圆或者其他形状。

road.jpg

result.jpg

算法

在Canny算法提取出边缘的基础上,

  1. 将原图像空间转到参数空间;

  2. 在参数空间 (u,v) 内建立两维数组 A( u,v ) ;

  3. 在开始时将数组置零;

  4. 对图像空间的每一个待检测点 (xi, yi) ,令 u 取遍所有可能的取值,并计算对应的v

    v = - xi * u + yi

  5. 对计算得到的 (u,v) ,对 A(u,v) 中相应单元进行累加:

    A(u, v) = A(u, v) + 1

  6. 根据 A(u,v)的值,确定有多少点是共线的, 同时可以知道线条的参数(u,v)。

    y = u x + v

C++实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include <opencv2/opencv.hpp>
#include <math.h>

#define _USE_MATH_DEFINES
#define NUM_LINES 8
#define MIN_LINEPOINTS 30

using namespace cv;
using namespace std;


/**
Hough变换(找到最长的NUM_LINES条直线)
img为输入的二值边缘图像,lines为Hough变换提取出的直线
*/
int houghTrans_max(Mat img, vector<Vec2f> &lines) {
int numangle = 360;
int numr = int(sqrt(pow(img.rows, 2) + pow(img.cols, 2)));
int numpoint[numangle][numr];
int numline = 0;
// numpoint置0
for (int i = 0; i < numangle; i++) {
for (int j = 0 ; j < numr; j++) {
numpoint[i][j] = 0;
}
}
// 循环遍历每个点,如果为边缘点则进行处理(遍历每个角度ang,算出相应的r)
for (int j = 0; j < img.rows; j++) {
for (int i = 0; i < img.cols; i++) {
// 只取边缘点
if (img.ptr<uchar>(j)[i] == 255) {
// 遍历每个角度ang,算出相应的r
for (int n = 0; n < numangle/2; n++) {
double ang = double(n*M_PI/180);
int r = int(i*cos(ang) + j*sin(ang));
if(r < 0){
int n1 = n+180;
int r1 = -r;
numpoint[n1][r1] ++;
} else {
numpoint[n][r] ++; //记录该角度半径下的点数
}
}
}
}
}

int minmax = 0;
int maxNum[NUM_LINES];
for (int i = 0; i < NUM_LINES; i++) {
maxNum[i] = 0;
}
float lines_f[NUM_LINES][2];
// 遍历角度半径
for (int i = 0; i < numangle; i++) {
for (int j = 0 ; j < numr; j++) {
// 如该角度、半径下点数大于门限10,且进行非极大值抑制,则记录线
if (numpoint[i][j] > MIN_LINEPOINTS && (numpoint[i][j] > minmax || numline < NUM_LINES)) {
// 未满四条线时
if (numline < NUM_LINES) {
maxNum[numline] = numpoint[i][j];
lines_f[numline][0] = float(j);
lines_f[numline][1] = float(i*M_PI/180);
minmax = maxNum[0];
for (int m = 0; m < numline+1; m++) {
if (maxNum[m] < minmax) {
minmax = maxNum[m];
}
}
}
// 满四条线时
else {
for (int m = 0; m < NUM_LINES; m++) {
if (maxNum[m] == minmax) {
lines_f[m][0] = float(j);
lines_f[m][1] = float(i*M_PI/180);
maxNum[m] = numpoint[i][j];
break;
}
}
minmax = maxNum[0];
for (int m = 0; m < NUM_LINES; m++) {
if (maxNum[m] < minmax) {
minmax = maxNum[m];
}
}
}
numline++;
}
}
}
if (numline > NUM_LINES) {
numline = NUM_LINES;
}
int n = 0;
for (int i = 0; i < numline; i++) {
Vec2f l;
l[0] = lines_f[i][0];
l[1] = lines_f[i][1];
lines.push_back(l);
n++;
}
return numline;
}

/**
Hough变换
img为输入的二值边缘图像,lines为Hough变换提取出的直线,threshold是共线的点数的阈值
*/
int houghTrans(Mat img, vector<Vec2f> &lines, int threshold) {
int numangle = 360;
int numr = int(sqrt(pow(img.rows, 2) + pow(img.cols, 2)));
int numpoint[numangle][numr];
int numline = 0;
// numpoint置0
for (int i = 0; i < numangle; i++) {
for (int j = 0 ; j < numr; j++) {
numpoint[i][j] = 0;
}
}
// 循环遍历每个点,如果为边缘点则进行处理(遍历每个角度ang,算出相应的r)
for (int j = 0; j < img.rows; j++) {
for (int i = 0; i < img.cols; i++) {
// 只取边缘点
if (img.ptr<uchar>(j)[i] == 255) {
// 遍历每个角度ang,算出相应的r
for (int n = 0; n < numangle/2; n++) {
double ang = double(n*M_PI/180);
int r = int(i*cos(ang) + j*sin(ang));
if(r < 0){
int n1 = n+180;
int r1 = -r;
numpoint[n1][r1] ++;
} else {
numpoint[n][r] ++; //记录该角度半径下的点数
}
}
}
}
}

// 遍历角度半径
for (int i = 0; i < numangle; i++) {
for (int j = 0 ; j < numr; j++) {
// 如该角度、半径下点数大于门限,且进行非极大值抑制,则记录线
if (numpoint[i][j] > threshold) {
Vec2f l;
l[0] = float(j);
l[1] = float(i*M_PI/180);
lines.push_back(l);
numline ++;
}
}
}
return numline;
}

/**
在图像上画线
img为输入图像,result为画完线的输出图像,lines为需要画的线
*/
void drawLines(Mat &img, Mat &result, vector<Vec2f> &lines){
cvtColor(img, result, COLOR_GRAY2BGR); //将IMREAD_GRAYSCALE转化为COLOR_GRAY2BGR格式,以便显示线
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
if (rho/b < 0) {
pt1.y = 0;
pt1.x = cvRound(rho/a);
} else {
pt1.x = 0;
pt1.y = cvRound(rho/b);
}
if ((-(a/b)*float(img.cols-1)+rho/b) >= img.rows) {
pt2.y = img.rows-1;
pt2.x = cvRound(-(b/a)*float(img.rows-1) + rho/a);
} else {
pt2.x = img.cols-1;
pt2.y = cvRound(-(a/b)*float(img.cols-1) + rho/b);
}
line( result, pt1, pt2, Scalar(0,0,255), 1, LINE_AA);
}
}


int main() {
Mat img, edge, result;
img = imread("road.jpg", IMREAD_GRAYSCALE); //从文件中加载灰度图像

// 读取图片失败,则停止
if (img.empty()) {
printf("读取图像文件失败");
system("pause");
return -1;
}

Canny(img, edge, 100, 400); // canny变换

vector<Vec2f> lines;
// int numline = houghTrans_max(edge, lines); // Hough变换(找到最长的NUM_LINES条直线)
// HoughLines(edge, lines, 1, CV_PI/180, 150, 0, 0 ); // OpenCV的hough变换
houghTrans(edge, lines, 400);

drawLines(img, result, lines);// 画线

// 图像显示
imshow("img", img);
imshow("canny",edge);// 图像显示
imshow("hough", result);
waitKey();
imwrite("result.jpg", result);
return 0;
}

结果

原图像:

road.jpg

hough变化后的图像(找到直线用红线标出):

result.jpg