Matlab实现 通过检测QR二维码位置探测图案进行精确定位

近邻点集融合算法

QR二维码结构简介

QR二维码识别中在对图像预处理后最重要的一步就是要进行定位,QR二维码中有三个位置探测图形,通过扫描其特征便可以进行精细定位。

QR二维码结构
其中位置探测图形的比例特征如下:

QR二维码位置探测图形
本文介绍的方法用于在二维码经过图像预处理并已经粗定位二维码大致位置后,检测位置探测图案坐标以精确定位。
首先要做的是,横竖扫描图像像素,找到满足黑白黑白黑且比例关系近似1:1:3:1:1的,将其两端的中点坐标记录下来,最终得到整幅图像所有的候选点,代码附在文末。
效果如图:

扫描得到的所有候选点
下一步使用原创的近邻点融合算法筛选出真正的位置探测图形所在位置的点。

近邻点融合

1、搜索每个点附近一定距离内近邻点的数量,用每个点近邻点集的平均坐标代替原本的坐标:这样可以将比较集中的点的集群向内收缩融合,孤立点由于没有紧邻点则会直接删除,在不停的迭代中其他零散的点的紧邻点减少的速度会远远大于三个位置探测图形所在位置相对更集中的点。
融合效果如左图,右图是每个近邻点集求出平均坐标后的效果。
各近邻点集紧邻点集平均坐标
2、当得到的近邻点集数量小于某个值时(默认定为20)或者迭代次数到达预设的最大迭代次数(10),结束迭代。
3、筛选出拥有紧邻点最多的三个点,求出其平均坐标就是三个位置探测图形所在的坐标了。
效果如图:
在这里插入图片描述
后续只需要通过判断这三个点的位置关系,对图像进行校正即可。
更新:
使用400张左右的二维码图片进行测试,其中包含了不同方向不同角度,以上方法得到的定位准确率仅有70%不到,因此算法仍需优化。
思考后加入了一个反馈机制,对于得到的三个坐标进行计算,判断其各种位置关系是否是真正的三个位置探测图形,若计算结果发现位置关系差别较大,则对近邻点融合算法的参数进行调整重新融合,相关代码已更新。

经过实验对比,该算法准确率可以提高到89%左右,单次耗时在Matlab下为2秒左右,而 https://blog.csdn.net/c602273091/article/details/42033069 中提到的方法在matlab下的准确略能达到95%,且运行速度普遍低于1秒,集中在0.8-0.9秒,但是实验发现几乎所有错误都集中在前200张图片,因此该方法在普适情况下更加适用,但是在某些情况下近邻点融合算法能略占优势。

本文所述算法以及 https://blog.csdn.net/c602273091/article/details/42033069 中算法的matlab实现(命名为group_points.m)已打包上传,链接如下:
https://download.csdn.net/download/whitett/11139476
不知道为啥要5个积分,需要可以评论联系我。

QR二维码精定位算法:

%横竖扫描图像像素,找到满足黑白黑白黑且比例关系近似1:1:3:1:1的,
%将其两端的中点坐标记录下来,最终得到整幅图像所有的候选点,
%然后下一步筛选出真正的位置探测图形所在位置的点。
%紧邻点融合算法:
%1、搜索每个点附近一定距离内近邻点的数量,用
%每个点近邻点集的平均坐标代替原本的坐标:这样可以将比较集中的点
%的集群向内收缩融合,孤立点由于没有紧邻点则会直接删除,在不停的
%迭代中其他零散的点的紧邻点减少的速度会远远大于三个位置探测图形
%所在位置相对更集中的点。
%2、当拥有近邻点集的点数量小于某个值时(默认定为20)或者
%迭代次数到达预设的最大迭代次数时结束迭代。
%3、筛选出拥有紧邻点最多的三个点,求出其平均坐标就是三个
%位置探测图形所在的坐标了。
%4、判断得到的三个点坐标是否是正确的三个位置探测图形位置,
%若不是,则调整参数进行二次计算,直到判断通过。

function points = detect(img)
clc;close all;
[m, n,~] = size(img);
tol = 5; %初始距离阈值

points = findInRowAndCol(img); %横纵像素扫描,找到拥有1:1:3:1:1比例的位置

% tol = 4;     
iterate =10; %最大迭代次数
num = 0;
itnum = 0;
numMax = 30; %结束迭代条件,小于numMax个点则结束

for i = 1:iterate
    [num, points,numRepeat] = Points_merge(tol,points); %近邻点融合函数
    itnum = itnum + 1;
    if(num < numMax)  %小于numMax个点集结束迭代
        break;
    end
end

% 排序,前三个即为紧邻点数量最多的三个点
for i = 1:num-1
    for j = i:num
        if(numRepeat(i) < numRepeat(j))
            temp = numRepeat(i);
            numRepeat(i) = numRepeat(j);
            numRepeat(j) = temp;
            temp1 =  points(i,1);
            points(i,1) =  points(j,1);
            points(j,1) = temp1;
            temp2 =  points(i,2);
            points(i,2) =  points(j,2);
            points(j,2) = temp2;
        end
    end
end

%反馈,若三点不满足一定条件则进行二次计算重新定位
count = 0;  %计算重复定位次数
Feedback = detectFeedback(points);
if(Feedback == 0)
    points = dectectAgain1(img, tol, numMax, count)
end

%画出拥有近邻点集做多的前三个点的平均坐标
for index = 1:3
    x(index) = points(index,1);
    y(index) = points(index,2);
end
figure(1);
subplot(1,1,1);
imshow(img);
hold on
for index = 1:3
    plot(y(index),x(index),'*r');
end
grid on

扫描1:1:3:1:1位置探测图案的代码:

%横、纵两个方向扫描图像像素,找到满足黑白黑白黑且
%比例关系近似1:1:3:1:1的,将其两端的中点坐标记录下来,
%最终得到整幅图像所有的候选点。

function pointAll = findInRowAndCol()
clear;close all;clc;
f = imread('test.jpg');
k=graythresh(f);
img=im2bw(f,k);
img = 255*img;

[rows, cols] = size(img);

skipRows = 1;
currentState = 0;
pointRow = [];
pointCol = [];
% C = [];
 
%横向扫描
for row = 1:skipRows:rows
     stateCount(1) = 0; % 占比为1的黑色像素
     stateCount(2) = 0; % 占比为1的白色像素
     stateCount(3) = 0; % 占比为3的黑色像素
     stateCount(4) = 0; % 占比为1的白色像素
     stateCount(5) = 0; % 占比为1的黑色像素
     currentState = 1;  % 模块属性计数器(黑色像素模块/白色像素模块)
     
     ptr_row = img(row,:);
     
     for col = 1:cols
         if(ptr_row(col) < 128)
             %该像素为黑色
             if(mod(currentState, 2) == 0)
                 %若当前计数器为白色模块计数器,将其转入下一级计数器即黑色模块计数器
                 currentState = currentState + 1;
             end
             stateCount(currentState) = stateCount(currentState) + 1;
         else
             %该像素为白色
             if(mod(currentState, 2) == 0)
                 %若当前计数器为白色模块计数器,将其转入下一级计数器即黑色模块计数器
                 stateCount(currentState) = stateCount(currentState) + 1;
             else
                 if(currentState == 5)
                     %在最后一个黑色模块计数器后出现了一个白色像素,即已经获取了黑 白 黑 白 黑的连续像素片段
                     %需要开始通过1:1:3:1:1判断前5个模块是否是位置探测图形的特征
                     [check,totalFinderSize] = QRcheckRatio(stateCount);%判断是否符合1:1:3:1:1
%                      C = [C,check];
                     if(check == 1)
                         %满足比例,记入
                         x = row;
                         y = col - totalFinderSize/2;
                         pointRow = [pointRow;[x,y]];
                         currentState = 1;
                         stateCount(1) = 0;
                         stateCount(2) = 0;
                         stateCount(3) = 0;
                         stateCount(4) = 0;
                         stateCount(5) = 0; 
                     else
                            % 不满足比例,继续扫描
                            currentState = 4;
                            stateCount(1) = stateCount(3);
                            stateCount(2) = stateCount(4);
                            stateCount(3) = stateCount(5);
                            stateCount(4) = 1;
                            stateCount(5) = 0;
                     end
                 else
                        % 不是最后一个黑色模块计数器5
                        % 转换为白色模块计数器
                        currentState = currentState + 1;
                        stateCount(currentState) = stateCount(currentState) + 1;
                 end
             end
         end
     end 
end
 
%纵向扫描
for col = 1:skipRows:cols
     stateCount(1) = 0; % 占比为1的黑色像素
     stateCount(2) = 0; % 占比为1的白色像素
     stateCount(3) = 0; % 占比为3的黑色像素
     stateCount(4) = 0; % 占比为1的白色像素
     stateCount(5) = 0; % 占比为1的黑色像素
     currentState = 1;  % 模块属性计数器(黑色像素模块/白色像素模块)
     
     ptr_col = img(:,col);
     
     for row = 1:rows
         if(ptr_col(row) < 128)
             %该像素为黑色
             if(mod(currentState, 2) == 0)
                 %若当前计数器为白色模块计数器,将其转入下一级计数器即黑色模块计数器
                 currentState = currentState + 1;
             end
             stateCount(currentState) = stateCount(currentState) + 1;
         else
             %该像素为白色
             if(mod(currentState, 2) == 0)
                 %若当前计数器为白色模块计数器,将其转入下一级计数器即黑色模块计数器
                 stateCount(currentState) = stateCount(currentState) + 1;
             else
                 if(currentState == 5)
                     %在最后一个黑色模块计数器后出现了一个白色像素,即已经获取了黑 白 黑 白 黑的连续像素片段
                     %需要开始通过1:1:3:1:1判断前5个模块是否是位置探测图形的特征
                     [check,totalFinderSize] = QRcheckRatio(stateCount);%判断是否符合1:1:3:1:1
                     if(check)
                         x = row - totalFinderSize/2;
                         y = col;
                         pointCol = [pointCol;[x,y]];
                         currentState = 1;
                         stateCount(1) = 0;
                         stateCount(2) = 0;
                         stateCount(3) = 0;
                         stateCount(4) = 0;
                         stateCount(5) = 0; 
                     else
                            % 不满足比例,继续扫描
                            currentState = 4;
                            stateCount(1) = stateCount(3);
                            stateCount(2) = stateCount(4);
                            stateCount(3) = stateCount(5);
                            stateCount(4) = 1;
                            stateCount(5) = 0;
                     end
                 else
                        % 不是最后一个黑色模块计数器5
                        % 转换为白色模块计数器
                        currentState = currentState + 1;
                        stateCount(currentState) = stateCount(currentState) + 1;
                 end
             end
         end
     end 
end

%画点
pointAll = [pointRow; pointCol];
x = pointAll(:,1);
y = pointAll(:,2);
subplot(1,1,1)
imshow(img);
hold on
plot(y,x,'*r');
grid on

其中判断是否满足比例关系的函数代码如下:

function [bool,totalFinderSize] = QRcheckRatio(stateCount)

totalFinderSize = 0;    
for i = 1:5
    count = stateCount(i);
    totalFinderSize = totalFinderSize +  count;
    if(count == 0)
        bool = false;
    end
end
if(totalFinderSize<7)
    bool = false;
end
moduleSize = ceil(totalFinderSize / 7); % 单个模块平均宽度
    
%计算比例 1:1:3:1:1
tol_factor = 0.5; %用于判断比例的误差系数
maxVariance = moduleSize*tol_factor;
retVal = ((abs(moduleSize - (stateCount(1)))< maxVariance) &&...
    (abs(moduleSize - (stateCount(2)))< maxVariance) &&...
    (abs(3*moduleSize - (stateCount(3)))< 3*maxVariance) &&...
    (abs(moduleSize - (stateCount(4)))< maxVariance) &&...
    (abs(moduleSize - (stateCount(5)))< maxVariance));

bool = retVal;

扫描结果如图:
扫描得到的所有候选点

近邻点集融合算法代码如下:

%紧邻点融合
%搜索每个点附近一定距离内近邻点的数量,用每个点近邻点集
%的平均坐标代替原本的坐标,比较集中的点的集群将会向内
%收缩融合,孤立点直接删除。

function [num1, points,numRepeat] = Points_merge(tol, points)
clc;close all;

tol_distance = tol; 
[num, ~] = size(points);
xgroup = [];
ygroup = [];
score(1:num) = 0;
xgroup(1:num) = 0;
ygroup(1:num) = 0;

%删除重叠点
for i = 1:num-1
    for j = i+1:num
        if(points(i,1) == points(j,1) && points(i,2) == points(j,2))
            points(i,1) = 0;
            points(i,2) = 0;
        end
    end
end

%距离较近的点记入近邻点集,用以求平均
for i = 1:num
    for j = 1:num
        distance = sqrt((points(i,1)-points(j,1))*(points(i,1)-points(j,1))+...
            (points(i,2)-points(j,2))*(points(i,2)-points(j,2)));
        if (distance < tol_distance)
            score(i) = score(i) + 1;
            %将相近点的坐标相加,为后续求平均距离做准备
            xgroup(i) = xgroup(i) + points(j,1);
            ygroup(i) = ygroup(i) + points(j,2);
        end
    end
end

%计算每个点的平均坐标,并用其替换掉原本的点坐标
for i = 1:num
    points(i,1)= xgroup(i)/ score(i);
    points(i,2)= ygroup(i)/ score(i);
    if(score(i) == 1)
        points(i,1)= 0;
        points(i,2)= 0;
    end
end

%通过上一步计算的平均坐标 合并拥有相同坐标的点,即拥有相同近邻点集的点
numRepeat(1:num) = 1; %记录近邻点集数量的计数器
k = 0;
for i = 1:num-1  %将重复项化为零
    if(points(i,1)~= 0 && points(i,2)~=0)%遇到非零点计数器才会加一
        k = k+1;
    end
    for j = i+1 : num
        if(points(i,1) == points(j,1) && points(i,2) == points(j,2) ...
                && points(i,1)~= 0 && points(i,2)~=0)
            numRepeat(k) = numRepeat(k)+1;
            points(j,1)= 0;%坐标相同的点清为零,等下一步将其剔除
            points(j,2)= 0;
        end
    end
end
%非零项往前移,将零项往后移
i = 1;
while i ~= num  
    if (points(i,1) == 0 && points(i,2) == 0)
        for index = i:num-1
            points(index,1) = points(index+1,1);
            points(index,2) = points(index+1,2);
            points(num,1) = 0;
            points(num,2) = 0;
        end
    else
        i = i + 1;
    end
    %判断是否已将所有非零项和零项分开
    is = issort(points);
    if(is)
        i = num;
    end
end

%非零项的数量
num1 = 0;
for i = 1:num
    if (points(i,1) ~= 0 || points(i,2) ~= 0)
        num1 = num1 + 1;
    end
end

% %画点
% img = imread('rotate.jpg');
% for index = 1:num1
%     x(index) = points(index,1);
%     y(index) = points(index,2);
% end
% figure
% subplot(1,1,1)
% imshow(img);
% hold on
% for index = 1:num1
%     plot(y(index),x(index),'*r');
% end
% grid on

end

其中判断是否已将所有非零项和零项分开的函数代码如下:

%判断所有非零点和零点是否已经被分开

function is = issort(points)

[num,~] = size(points);

for i = 1:num
    if (points(i,1) == 0 && points(i,2) == 0)
        for k = i+1:num
           if (points(k,1) ~= 0 || points(k,2) ~= 0)
               is = 0;
               return;
           end
           if(k == num)
               is = 1;
               return;
           end
        end
    elseif (i == num)
        is = 1;
        return;
    end
end

判断得到的三个点是否是正确的三个位置探测图形坐标:

function Feedback = detectFeedback(points)

Feedback = 1;
result = 1;
point1 = points(1,:); %取三点
point2 = points(2,:);
point3 = points(3,:);
vector12 = point2 - point1; %求向量,区分正负
vector21 = point1 - point2;
vector23 = point3 - point2;
vector32 = point2 - point3;
vector13 = point3 - point1;
vector31 = point1 - point3;
length1 = norm(vector12); %三条边长
length2 = norm(vector23);
length3 = norm(vector31);
%三边夹角
angle1 = 180/pi * acos(dot(vector12, vector13)/(norm(vector12)* norm(vector13)));
angle2 = 180/pi * acos(dot(vector23, vector21)/(norm(vector23)* norm(vector21)));
angle3 = 180/pi * acos(dot(vector31, vector32)/(norm(vector31)* norm(vector32)));

%条件
%三边长度差距不能过大,设定为不得超过2.5倍
if(length1 < length2/2.5 || length1 < length3/2.5 || ...
        length2 < length1/2.5 || length2 < length3/2.5 || ...
        length3 < length1/2.5 || length3 < length2/2.5)
    result = 0;
%三边夹角不得过大,经实验设定为120度
elseif(angle1 > 120 || angle2 > 120 || angle3 > 120)
    result = 0;
%点在x方向上距离较近时的情况进行排除,
%该情况下三点几乎呈现等边三角形并且x坐标值距离较近
elseif(angle1 < 70 && angle2 < 70 && angle3 < 70 &&...
        (abs(point1(2)-point2(2)) > length1/3.5 &&...
        abs(point2(2)-point3(2)) > length2/3.5 &&...
        abs(point1(2)-point3(2)) > length3/3.5))
    result = 0;
end

%最终判断
if(result == 0)
    Feedback = 0;
end

二次计算循环块1

function points = dectectAgain1(img, tol, numMax, count)
clc;close all;
count = count + 1; %重复定位次数增加一次

points = findInRowAndCol(img); %横纵像素扫描,找到拥有1:1:3:1:1比例的位置

tol = tol + count;     %初始距离阈值
iterate =10; %最大迭代次数
num = 0;
itnum = 0;
numMax = numMax + 2; %结束迭代条件,小于numMax个点则结束

for i = 1:iterate
    [num, points,numRepeat] = Points_merge(tol,points); %近邻点融合函数
    itnum = itnum + 1;
    if(num < numMax)  %小于numMax个点集结束迭代
        break;
    end
end

% 排序,前三个即为紧邻点数量最多的三个点
for i = 1:num-1
    for j = i:num
        if(numRepeat(i) < numRepeat(j))
            temp = numRepeat(i);
            numRepeat(i) = numRepeat(j);
            numRepeat(j) = temp;
            temp1 =  points(i,1);
            points(i,1) =  points(j,1);
            points(j,1) = temp1;
            temp2 =  points(i,2);
            points(i,2) =  points(j,2);
            points(j,2) = temp2;
        end
    end
end

%反馈
Feedback = detectFeedback(points);
if(Feedback == 0 && tol > 1)
    points = dectectAgain2(img, tol, numMax, count);
end

二次计算循环块2

function points = dectectAgain2(img, tol, numMax, count)
clc;close all;
count = count + 1; %重复定位次数增加一次

points = findInRowAndCol(img); %横纵像素扫描,找到拥有1:1:3:1:1比例的位置

tol = tol - count;     %初始距离阈值
iterate =10; %最大迭代次数
num = 0;
itnum = 0;
numMax = numMax + 2; %结束迭代条件,小于numMax个点则结束

for i = 1:iterate
    [num, points,numRepeat] = Points_merge(tol,points); %近邻点融合函数
    itnum = itnum + 1;
    if(num < numMax)  %小于numMax个点集结束迭代
        break;
    end
end

% 排序,前三个即为紧邻点数量最多的三个点
for i = 1:num-1
    for j = i:num
        if(numRepeat(i) < numRepeat(j))
            temp = numRepeat(i);
            numRepeat(i) = numRepeat(j);
            numRepeat(j) = temp;
            temp1 =  points(i,1);
            points(i,1) =  points(j,1);
            points(j,1) = temp1;
            temp2 =  points(i,2);
            points(i,2) =  points(j,2);
            points(j,2) = temp2;
        end
    end
end

%反馈
Feedback = detectFeedback(points);
if(Feedback == 0 && tol > 1)
    points = dectectAgain1(img, tol, numMax, count);
end

以上均为个人学习所得,欢迎分享交流。
本文参考: https://blog.csdn.net/c602273091/article/details/43901137

评论 60
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值