引言:
大一寒假放的時間挺長的,在家空着便想着搞點什麼軟件玩玩,想到前不久學的點圖像處理基礎知識,便想將它們整合一下,弄個實在的東西,於是便想起了這個。我就先僅涉及一下它的棋子識別的代碼的matlab仿真,也算一時興起了吧
總體思路:
由攝像頭定期對棋盤進行拍攝,將拍攝的照片傳給matlab,matlab將這次拍攝的照片與上次拍攝的照片進行圖像相減運算,得到一個新圖像,如果這個新圖像的所有像素點求和小於一個值,則說明兩張圖差不多,因而可以說棋盤上沒有下新棋,繼續上述過程;反之則對新圖像進行圖像處理,運用hough圓檢測找該棋子,並通過hough直線檢測找到棋盤的橫縱線,由此判斷出該棋子在第幾行,第幾列,從而判斷出棋子位置。
matlab代碼實現:
1:首先我們要有一個攝像頭,將採集的圖像數據每隔一定時間及時傳送給matlab:
步驟:
A:將攝像頭與電腦usb連接並下載驅動程序
B:用matlab查看本機連有的攝像頭,輸入命令imaqhwinfo,顯示結果爲:
InstalledAdaptors: {'gentl' 'gige' 'matrox' 'winvideo'}
MATLABVersion: '8.6 (R2015b)'
ToolboxName: 'Image Acquisition Toolbox'
ToolboxVersion: '4.10 (R2015b)'
C:查看’winvideo’攝像頭的id號,輸入命令imaqhwinfo( ‘winvideo’ ),顯示結果爲:
AdaptorDllName: 'D:\matlab\matlab\osgenericvideointerface\toolbox\imaq\supportpackages\genericvideo\…'
AdaptorDllVersion: '4.10 (R2015b)'
AdaptorName: 'winvideo'
DeviceIDs: {[1]}
D:每隔3秒讀取攝像頭圖像:
clear;
vid = videoinput('winvideo',1);%通過videoinput()創建視頻輸入對象
preview(vid);%顯示所創建的視頻對象
start(vid);%開始vid視頻輸入對象定時
h=figure('NumberTitle','off','Name','視頻',...
'MenuBar','none','color','c',...
'Position', [0, 0, 1, 1], 'Visible', 'on'); %新建讀取的圖像窗口
while ishandle(h)%判斷是否有效的圖像對象句柄
frame1=getsnapshot (vid); % 捕獲前一張圖像
flushdata(vid);%清除數據獲取引擎的所有數據、置屬性SamplesAvailable爲0
imshow(a);
pause(3);%等3秒後獲取下一張圖片
frame2=getsnapshot (vid);% 捕獲後一張圖像
flushdata(vid);
end;
2:接着對圖像進行減運算,判斷是否有新棋子出現(假設對方是白棋):
frame=imsubtract(frame2,frame1);%圖像相減
frame=im2bw(frame,220/255);%設定閾值,將白棋找出,去掉黑棋
num=sum(sum(frame),2);%求相減新圖像的像素點之和
if num>1000%有新白棋出現
h=[-1 -1 -1;-1 8 -1;-1 -1 -1];%拉普拉斯卷積核
frame=imfiter(frame,h);%提取黑棋的邊界
end
3:接着用hough圓檢測檢測新棋子圓心座標
[hough_space,hough_circle,para]=hough_circle(iii,8,0.05,50,70,0.6);% 調用houghcircle函數(函數代碼在後面)
4:最後,通過hough直線檢測及一些方法判斷該棋子在棋盤的
%調用hough直線檢測庫函數hough,houghpeaks,houghlines
[h,t,r]=hough(bw_picture);
p=houghpeaks(h,80,'threshold',ceil(0.5*max(h(:))),'threshold',120);
lines=houghlines(bw_picture,t,r,p,'FillGap',10,'MinLength',20);%檢測到的直線放在lines結構體中
max_len = 0;
for k=1:length(lines)
xy=[lines(k).point1;lines(k).point2];
len = norm(lines(k).point1 - lines(k).point2);
if ( len > max_len)
max_len = len;
xy_long = xy;
end
end
%判斷直線是橫線還是豎線
count1=1;%計數橫線個數
count2=1;%計數豎線個數
for k=1:length(lines)
xy=[lines(k).point1;lines(k).point2];
if abs(xy(1,2)-xy(2,2))<20%%橫線
horizontal_lines(:,:,count1)=xy;
count1=count1+1;
end
if abs(xy(1,1)-xy(2,1))<20%%豎線
vertical_lines(:,:,count2)=xy;
count2=count2+1;
end
end
lines1=zeros(1,size(horizontal_lines,3));%棋盤的行
columns1=zeros(1,size(vertical_lines,3));%棋盤的列
for horizontal_count=1:size(horizontal_lines,3)-1
if abs(horizontal_lines(2,2,horizontal_count)-horizontal_lines(1,2,horizontal_count+1))<50%是否同一行
continue;
else
lines1(horizontal_count)=horizontal_lines(1,2,horizontal_count);
end
end
%找出棋盤的行
lines=lines1>0;
lines=lines1(lines);
lines=sort(lines);
for vertical_count=1:size(vertical_lines,3)-1
if abs(vertical_lines(2,1,vertical_count)-vertical_lines(1,1,vertical_count+1))<50 %是否同一列
continue;
else
columns1(vertical_count)=vertical_lines(1,1,vertical_count+1);
end
end
%找出棋盤的列
columns=columns1>0;
columns=columns1(columns);
columns=sort(columns);
%找出每個棋子所在行與列,找絕對值最小值
minline_count=1;%計數第幾行爲棋子所在行
mimcolumn_count=1;;%計數第幾列爲棋子所在列
min_lines=500;%棋子橫座標與棋盤橫列之間距離的初值
min_columns=500;%棋子縱座標與棋盤縱列之間距離的初值
for chess_num=1:size(lines)%多少行
if abs(para(1,chess_num)-lines(chess_num))<min
min_lines=para(1,chess_num)-lines(chess_num);
minline_count=minline_count+1;
line=minline_count-1;
end
end
for chess_num=1:size(columns)%多少列
if abs(para(2,chess_num)-columns(chess_num))<min
min_columns=para(1,chess_num)-lines(chess_num);
mincolumn_count=mincolumn_count+1;
column=mincolumn_count-1;
end
end
最後(line,column)就是新下的棋所在的棋子橫列。
以上代碼實現了對方新下白棋的圖像識別,並能反饋出該棋子所在行與列。
PS:
1:關於hough變換的基本原理簡介:(簡單介紹,一帶而過,數學細節略)
我們知道,一條直線在直角座標系下可以用y=kx+b表示, 霍夫變換的主要思想是將該方程的參數和變量交換,即用x,y作爲已知量k,b作爲變量座標,所以直角座標系下的直線y=kx+b在參數空間表示爲點(k,b),而一個點(x1,y1)在直角座標系下表示爲一條直線y1=x1·k+b,其中(k,b)是該直線上的任意點。爲了計算方便,我們將參數空間的座標表示爲極座標下的γ和θ。因爲同一條直線上的點對應的(γ,θ)是相同的,因此可以先將圖片進行邊緣檢測,然後對圖像上每一個非零像素點,在參數座標下變換爲一條直線,那麼在直角座標下屬於同一條直線的點便在參數空間形成多條直線並內交於一點。因此可用該原理進行直線檢測。
X–Y平面上任意一條直線y = ax + b,對應在參數a-b平面上都有一個點
如果點(x1,y1)與點(x2,y2)共線,則這兩個點在參數a-b平面上的直線將有一個交點
霍夫圓變換的基本原理和上面講的霍夫線變化大體上是很類似的,只是點對應的二維極徑極角空間被三維的圓心點x, y還有半徑r空間取代。說“大體上類似”的原因是,如果完全用相同的方法的話,累加平面會被三維的累加容器所代替:在這三維中,一維是x,一維是y,另外一維是圓的半徑r。這就意味着需要大量的內存而且執行效率會很低,速度會很慢。
對直線來說, 一條直線能由參數極徑極角表示. 而對圓來說, 我們需要三個參數來表示一個圓(x,y,r).
2:matlab的hough直線檢測三個庫函數用法:
index:
1. [H,theta,rho] = hough(BW,p,v)
H是變換到的hough矩陣。
theta和rho對應於矩陣每一列和每一行的ρ和θ值組成的向量。
p與v成對使用。p如果使用thetaresolution則v是θ軸方向上的單位區間的長度,可取(0,90)之間,默認爲1;p如果使用rhoresolution 則v是ρ軸方向上的單位區間長度,可取(0,norm(size(BW))之間,默認爲1。
2. peaks = houghpeaks(H,numpeak,p,v)
peaks是一個Q*2的矩陣,每行的兩個元素分別是某一峯值的行列索引,Q爲找到的峯值數目。
numpeak是尋找的峯值數目。
p和v成對使用。p如果使用threshold則v表示峯值的閾值,只有大於閾值的點才被認爲可能是閾值,默認爲0.5*max(H(:))。p如果是NHoodSize則表示每次檢測出一個峯值後,v就指出該峯值周圍需要清零的鄰域信息,並以向量[M N] 形式輸出。
3. lines = houghlines(BW,theta,rho,peaks,p,v)
lines是個結構數組,有point1(端點1),point2(端點2),theta,rho。
p和v成對使用。p如果使用fillgap則v表示同一幅圖像中兩條線的閾值,小於將會合並,默認20。p如果是minlength則v表示直線段最小長度閾值,默認40。
point1:兩元素向量[r1, c1],指定了線段起點的行列座標。
point2:兩元素向量[r2, c2],指定了線段終點的行列座標。
theta:與線相關的霍夫變換的以度計量的角度。
rho:與線相關的霍夫變換的ρ軸位置
3:houghcircle函數的基本原理與代碼:
A:基本原理:
index:
(具體什麼數學原理略,數學渣渣表示很無奈)
首先對圖像應用邊緣檢測,比如用canny邊緣檢測。
【2】然後,對邊緣圖像中的每一個非零點,考慮其局部梯度,即用Sobel()函數計算x和y方向的Sobel一階導數得到梯度。
【3】利用得到的梯度,由斜率指定的直線上的每一個點都在累加器中被累加,這裏的斜率是從一個指定的最小值到指定的最大值的距離。
【4】同時,標記邊緣圖像中每一個非0像素的位置。
【5】然後從二維累加器中這些點中選擇候選的中心,這些中心都大於給定閾值並且大於其所有近鄰。這些候選的中心按照累加值降序排列,以便於最支持像素的中心首先出現。
【6】接下來對每一箇中心,考慮所有的非0像素。
【7】這些像素按照其與中心的距離排序。從到最大半徑的最小距離算起,選擇非0像素最支持的一條半徑。8.如果一箇中心收到邊緣圖像非0像素最充分的支持,並且到前期被選擇的中心有足夠的距離,那麼它就會被保留下來。
這個實現可以使算法執行起來更高效,或許更加重要的是,能夠幫助解決三維累加器中會產生許多噪聲並且使得結果不穩定的稀疏分佈問題。
B:代碼:
以下爲引用:(尊重知識產權從我從娃娃做起!!!)
function [hough_space,hough_circle,para] = hough_circle(BW,step_r,step_angle,r_min,r_max,p)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% input
% BW:二值圖像;
% step_r:檢測的圓半徑步長
% step_angle:角度步長,單位爲弧度
% r_min:最小圓半徑
% r_max:最大圓半徑
% p:閾值,0,1之間的數 通過調此值可以得到圖中圓的圓心和半徑
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% output
% hough_space:參數空間,h(a,b,r)表示圓心在(a,b)半徑爲r的圓上的點數
% hough_circl:二值圖像,檢測到的圓
% para:檢測到的圓的圓心、半徑
circleParaXYR=[];
para=[];
[m,n] = size(BW);
size_r = round((r_max-r_min)/step_r)+1;%四捨五入
size_angle = round(2*pi/step_angle);
hough_space = zeros(m,n,size_r);
[rows,cols] = find(BW);%查找非零元素的行列座標
ecount = size(rows);%非零座標的個數
% Hough變換
% 將圖像空間(x,y)對應到參數空間(a,b,r)
% a = x-r*cos(angle)
% b = y-r*sin(angle)
for i=1:ecount
for r=1:size_r %半徑步長數
for k=1:size_angle %按一定弧度把圓幾等分
a = round(rows(i)-(r_min+(r-1)*step_r)*cos(k*step_angle));
b = round(cols(i)-(r_min+(r-1)*step_r)*sin(k*step_angle));
if(a>0&a<=m&b>0&b<=n)
hough_space(a,b,r) = hough_space(a,b,r)+1;%h(a,b,r)的座標,圓心和半徑
end
end
end
end
% 搜索超過閾值的聚集點。對於多個圓的檢測,閾值要設的小一點!通過調此值,可以求出所有圓的圓心和半徑
max_para = max(max(max(hough_space)));%返回值就是這個矩陣的最大值
index = find(hough_space>=max_para*p);%一個矩陣中,想找到其中大於max_para*p數的位置
length = size(index);%符合閾值的個數
hough_circle = false(m,n);
%hough_circle = zeros(m,n);
%通過位置求半徑和圓心。
for i=1:ecount
for k=1:length
par3 = floor(index(k)/(m*n))+1;
par2 = floor((index(k)-(par3-1)*(m*n))/m)+1;
par1 = index(k)-(par3-1)*(m*n)-(par2-1)*m;
if((rows(i)-par1)^2+(cols(i)-par2)^2<(r_min+(par3-1)*step_r)^2+5&...
(rows(i)-par1)^2+(cols(i)-par2)^2>(r_min+(par3-1)*step_r)^2-5)
hough_circle(rows(i),cols(i)) = true;%檢測的圓
end
end
if(i>1000&&mod(i,1000)==0)
i
end
end
% 從超過峯值閾值中得到
for k=1:length
par3 = floor(index(k)/(m*n))+1;%取整
par2 = floor((index(k)-(par3-1)*(m*n))/m)+1;
par1 = index(k)-(par3-1)*(m*n)-(par2-1)*m;
circleParaXYR = [circleParaXYR;par1,par2,par3];
hough_circle(par1,par2)= true; %這時得到好多圓心和半徑,不同的圓的圓心處聚集好多點,這是因爲所給的圓不是標準的圓
%fprintf(1,'test1:Center %d %d \n',par1,par2);
end
%集中在各個圓的圓心處的點取平均,得到針對每個圓的精確圓心和半徑!
while size(circleParaXYR,1) >= 1
num=1;
XYR=[];
temp1=circleParaXYR(1,1);
temp2=circleParaXYR(1,2);
temp3=circleParaXYR(1,3);
c1=temp1;
c2=temp2;
c3=temp3;
temp3= r_min+(temp3-1)*step_r;
if size(circleParaXYR,1)>1
for k=2:size(circleParaXYR,1)
if (circleParaXYR(k,1)-temp1)^2+(circleParaXYR(k,2)-temp2)^2 > temp3^2
XYR=[XYR;circleParaXYR(k,1),circleParaXYR(k,2),circleParaXYR(k,3)]; %保存剩下圓的圓心和半徑位置
else
c1=c1+circleParaXYR(k,1);
c2=c2+circleParaXYR(k,2);
c3=c3+circleParaXYR(k,3);
num=num+1;
end
end
end
%fprintf(1,'sum %d %d radius %d\n',c1,c2,r_min+(c3-1)*step_r);
c1=round(c1/num);
c2=round(c2/num);
c3=round(c3/num);
c3=r_min+(c3-1)*step_r;
%fprintf(1,'num=%d\n',num)
%fprintf(1,'Center %d %d radius %d\n',c1,c2,c3);
para=[para;c1,c2,c3]; %保存各個圓的圓心和半徑的值
circleParaXYR=XYR;
end
寒假也沒學習什麼東西,效率也極低,也就沒能較好利用,表示遺憾。還是不上進,過於貪圖安逸享受,禁不住各種誘惑。希望下學期能更高效的學習,堅決杜絕邊學邊玩的不良現象!!!
希望以後有空能繼續挖掘發現matlab的更多功能。。。