引言:
大一寒假放的时间挺长的,在家空着便想着搞点什么软件玩玩,想到前不久学的点图像处理基础知识,便想将它们整合一下,弄个实在的东西,于是便想起了这个。我就先仅涉及一下它的棋子识别的代码的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的更多功能。。。