原文:http://blog.csdn.net/shanchuan2012/article/details/74010561
1.本文目的
結合實例和一些演示來理解霍夫變換中的精髓:空間轉換。
我想我把我對空間轉換的理解寫完了,這篇文章也就結束了,所以文章的內容不會那麼完整,不會完整的講霍夫變換(比如一般形狀的檢測,說實話我是初學也不會),也不會講在opencv中的實現(我只能抄代碼,自己也寫不出來)。
爲什麼要寫這篇文章?我是自學+初學圖像處理,沒有人可以問,只能上網搜索,網上的教程總有地方我看不明白,有時候我會懷疑是不是自己理解力不行?其實更多時候我覺得不是,問題其實在於兩點,一是教程講的不好,不能引領新手逐步深入,二是沒有找到合適的教程,不同階段看的東西是不一樣的。所以我把自己的理解寫出來,希望可以幫助跟我有同樣困惑的人,也希望自己不要誤導了別人,讓人產生更多的困惑。
2.簡介
這篇文章不是教程,不講基本概念,要了解霍夫變換可以先參考以下文章:
【OpenCV入門教程之十四】OpenCV霍夫變換:霍夫線變換,霍夫圓變換合輯-毛星雲
以一般化視角串聯霍夫變換(hough transform),從直線到圓再到廣義霍夫變換
看了以上資料我有很多地方是想不明白的,特別是看到檢測圓的時候,更別說一般形狀了。
剛看到霍夫變換的時候感覺挺神奇的,它來源於圖像處理中的幾何形狀檢測問題,用什麼辦法能夠檢測直線、圓或者別的形狀呢?最簡單的是直線,如果讓我來解決,要用什麼辦法才能把直線檢測出來呢?一時也想不到什麼辦法(最小二乘可以做擬合,但是一張圖上有多條直線怎麼辦?)。看到霍夫變換的時候想到了傅里葉變換,在這個空間不好解決的問題或者不明顯的特徵,給他來個變換,在另外一個空間上看問題,說不定就清楚了。
3.霍夫變換檢測直線
3.1笛卡爾座標下的例子
假如我們有如下幾個點:
它們完全在一條直線上,可以先告訴你們直線的方程是:。在我們不知情的情況下要如何檢測?
直線的一般表示是:,和是兩個參數,現在反過來,將兩個參數當做自變量和變量,將方程寫成:,這個時候座標軸就發生了變化,也就是空間發生了轉換,原來座標軸是,現在是。
以點 (該點所在空間此處稱爲圖像空間,因爲我們是在做圖像處理,這些點都對應於圖像的像素)爲例,代入直線方程 ,確定一條直線至少要兩個點,只有一個點是不能確定一條直線的,但可以知道的是,直線一定過這個點,過一個點的直線有無數條,像這樣(這個時候已經可以思考了,每個點上都有無數條直線經過,那麼怎麼找出同時經過這些點的直線?特徵是什麼?沒錯,用條件:“斜率和截距一樣”就可以把那條直線找出來,這也是做空間變換的原因):
由此得到: ,這是一條直線的方程:
所以在空間中的一個點,對應於空間(此處稱爲參數空間)上的一條線。這個比較好理解,所謂的空間,代表了直線的斜率和截距,既然過一個點的線有無數條,那麼斜率和截距也有無數個,空間上的線就描述了斜率和截距的無數種組合,所以形成了一條線。將圖像空間上所有對應的點在參數空間上的直線都畫出來就是這樣:
可以看到它們相交於一個點了,這個點是 ,沒錯,它就是我們要找的特徵點(前面說過要怎麼從無數條直線中找到經過這些點的直線),這正是圖像空間上所有點連成的直線的斜率和截距。這樣就把圖像空間上的那條直線檢測出來了,方程爲 。
但是實際上用極座標,爲什麼要用極座標而不用笛卡爾座標?上面給了回答,這裏不解釋。
3.2極座標下的例子
如何用一個角度和一段距離來表示過一個點的直線呢?方法是從原點作該直線的垂線,原點到該直線的距離用表示,垂線與x軸的夾角用表示,這樣就可以用來表示過一個點的任意直線了。
還是在笛卡爾座標下,的關係用來表示,可以寫爲(如果不明白,可以先看看參考資料,這裏面的示意圖講的很清楚):
還是一樣的思想,對於圖像空間上的一個點,過這個點的直線有無數條,所以也有無數個組合,那麼在參數空間就對應於一條線(這時是一條曲線,管他是什麼線呢,反正它們都是描述那無數條直線的參數組合)。還是以點 爲例,它對應於參數空間的線:
將圖像空間上所有對應的點在參數空間上的直線都畫出來就是這樣:
它們也相交於一點,把交點找到(我這裏沒有計算交點,就當它是吧,大概是 , 其實對應的是 , 這是我反推的)後,代入方程: 就可以得到過所有點的直線了:
整理後就是: .到此直線檢測結束。
4.霍夫變換檢測圓
都說檢測圓和檢測直線大體上是類似的,確實,從空間轉換的角度來說是一樣的,橢圓、任意形狀的檢測都是一樣的,只要找到合適的轉換方法。
圓的一般方程是這樣的:
爲圓心,爲半徑,還是先給一個例子:
圖上面有一系列的點,它們是在同一個圓上,這裏已經把圓的輪廓給出來了,這裏只畫了圓的一半,但已經足夠說明問題了。要檢測這個圖像中的圓應該怎麼辦?還是用同樣的思考方式,來個空間轉換。將上面圓的方程重新寫一下:
這時有三個參數,圓心,半徑。
過一點的圓有多少個?無數個。以上圖的第1個點爲例,把它代入方程,得到:,這個方程描述的圓都經過點,它們在參數空間上看起來是這樣的:
這裏取的是,沒取成無限的,要是取成無限了,圖就沒有辦法看了。每一個參數空間上的圈圈上的點都對應於圖像空間上經過點的一個圓,如下面這個點:
它代表了什麼意思呢?它代表了一個經過點的一個圓,圓心爲,半徑爲1(圖上的半徑),這樣是不是就清楚了一些?從三維空間來看就是這樣(爲了能看得清楚,只畫了半徑爲1,3,5的三個圓):
上面的圖是圖像空間中一個點對應在參數空間的曲線,將所有點對應在參數空間的曲線都畫出來就是這樣的(同樣爲了看的清楚,只取了3個半徑,分別爲1,3,5):
其實我們的目的就是在參數空間找聚集點,思想和檢測直線是一樣的,找參數空間中曲線交點最多的位置,可以看到在的時候所有圓都相交於同一個點,這個點對應的圓就是圖像空間中經過所有點的那個圓,這樣就把圓給檢測出來了。還可以看出,當r離真實值()越遠時,圓的相交的就越不明顯。
我想這時候對霍夫變換檢測直線和圓的原理應該說清楚了,從直線到圓,參數從2個變成了3個,對應的參數空間從2維變成了3維。如果是別的形狀,那麼它的參數可能更多,對應的參數空間維數就越多,這時就很難畫圖了。不過通過上面的理解,相信我們已經不需要再借助圖像來理解更高維的變換了,這時我們應該可以進行歸納了,不管多高維,我們都是去找在參數空間中曲線相交最多的位置。
5.數據不是那麼完美的情況
上面給出來的直線和圓都完全在同一條直線和同一個圓上,但實際上可能沒有那精確像下面這樣:
那麼參數空間中對應的相交點也不是完全在一個點上
圓的情況也是類似的,就不再給出。所以一般的做法是將參數空間分成很多個小格子,落在同一個格子裏面的就算同一個交點。
6.小結
我對霍夫變換的理解依然很膚淺,覺得奇妙的地方就是空間轉換。
這裏再來看以一般化視角串聯霍夫變換(hough transform),從直線到圓再到廣義霍夫變換對作者簡練的總結更能體會吧。
當然在實際的算法實現中,遠比上面的圖示來的複雜,不然後效率是個問題。
7.作圖代碼
這裏附上作圖用的matlab代碼:
% 霍夫變換理解 圓 2維
close('all');
% 給出一個圓,圓心(1,1),半徑3
r = 3;
x = -2 : 0.5 : 4; % x定義域
y = sqrt(r.^2 - (x-1).^2) + 1 + 0*(rand(1,length(x)) - 0.5); % 半個圓
% 圖像空間上的圓
figure;
plot(x,y,'*'); % 實際點
hold on;
x0 = -2 : 0.01 : 4;
y0 = sqrt(r.^2 - (x0-1).^2) + 1;
plot(x0,y0); % 圓曲線
hold off;
axis equal; % 各個軸比例相同
xlabel('x');
ylabel('y');
% 參數空間
figure;
% set(gcf,'position',[100,100,500,500]);
theta = 0 : 0.01 : 2*pi;
for ir = 1 : 5
for i = 1 : 1%length(x)
a = x(i) - ir*cos(theta); % 極座標
b = y(i) - ir*sin(theta);
plot(a,b); % 畫圓
hold on;
plot(x(i),y(i), '*'); % 畫圓心
end
end
hold off;
grid on;
set(gca, 'xtick', -6:1:8);
axis equal;
xlabel('a');
ylabel('b');
print(gcf, '-djpeg', 'c2_2.jpg', '-r72');
% 霍夫變換(Hough Transform)
% 直線
clc;clear;close('all');
% 測試點
x = [0,1,2,3,4];
y = x + 4 + 1*(rand(1,length(x))-0.5);
plot(x,y,'*');
hold on;
y1 = x + 4;
plot(x,y1);
xlabel('x');
ylabel('y');
print(gcf, '-djpeg', 'line1.jpg', '-r72');
% 笛卡爾座標
figure;
legd = {};
k = 0:3;
for i = 1 : length(x)
b = -x(i)*k + y(i);
plot(k,b)
hold on;
legd = [legd, num2str(i)];
end
hold off;
% legend(legd);
grid on;
xlabel('k');
ylabel('b');
print(gcf, '-djpeg', 'line2.jpg', '-r72');
% 極座標
figure;
theta = 0/180*pi : 0.1 : 180/180*pi;
for i = 1 : length(x)
ruo = x(i)*cos(theta) + y(i)*sin(theta);
plot(theta, ruo);
hold on;
end
hold off;
grid on;
xlabel('\theta');
ylabel('\rho');
print(gcf, '-djpeg', 'line3.jpg', '-r72');
% 霍夫變換理解 圓 3維
close('all');
% 給出一個圓,圓心(1,1),半徑3
r = 3;
x = -2 : 0.5 : 4; % x定義域
y = sqrt(r.^2 - (x-1).^2) + 1 + 0*(rand(1,length(x)) - 0.5); % 半個圓
% 圖像空間上的圓
figure;
plot(x,y,'*'); % 實際點
hold on;
x0 = -2 : 0.01 : 4;
y0 = sqrt(r.^2 - (x0-1).^2) + 1;
plot(x0,y0); % 圓曲線
hold off;
axis equal; % 各個軸比例相同
xlabel('x');
ylabel('y');
print(gcf, '-djpeg', 'c1.jpg', '-r72');
% 參數空間
% 3維圖畫的很慢,可能有地方弄錯了
figure;
% set(gcf,'position',[100,100,500,500]);
theta = 0 : 0.01 : 2*pi;
for ir = 1 : 2 : 5
for i = 1 : 1%length(x)
a = x(i) - ir*cos(theta); % 極座標
b = y(i) - ir*sin(theta);
r = ir*ones(1,length(a));
plot3(r,a,b,'b'); % 畫圓
hold on;
plot3(r,x(i),y(i), 'r*'); % 畫圓心
end
end
hold off;
grid on;
set(gca, 'xtick', -6:1:8);
% axis equal;
xlabel('r');
ylabel('a');
zlabel('b');
print(gcf, '-djpeg', 'c2.jpg', '-r72');