数字图像处理-包装生产线的质量检测

问题背景

当瓶子在传送带上运动,并通过自动装填机和封盖机进行包装时有如下图所示的情景。公司有一个图像系统,装备了有效捕捉静止图像的前端闪光照明设备。所以可以得到非常清晰的图像。当液体平面低于瓶颈底部和瓶子肩部的中间点时,认为瓶子未装满。瓶子的横断面上的倾斜部分及侧面定义为瓶子的肩部,瓶子在不断移动。据此设计一种检测瓶子未装满的方法。

设想和解决思路

假设流水线中瓶子、摄像机与流水线相对位置以及光线条件等保持不变,每一张待判断照片都能达到如上图所示的水平。我们可以采用两种不同的方法综合判断:
1.由于此瓶子在流水线上拍取照片时,双肩是最大的斜线段,甚至可以初略地认为,只有双肩是斜线段。由此,我们可以使用霍夫变换,求得这些双肩边缘的斜线段,选取斜线段的中点的水平线作为是否装满的标准。
2.根据假设,可以推测灰度图显示的空气区域大小与实际空气区域大小的比例保持不变。因此,我们可以通过将空气区域所占像素数量与标准数量比较来判断瓶子是否装满。
在实验中采用如下流程处理,综合利用两个方案,流程图如下图所示:

程序处理流程及结果

1. 腐蚀求得边缘

由于瓶子的边缘的光线较强,背景本身较暗,因此我们可以通过让背景腐蚀边缘,然后与原图像相减求得边缘。首先需要将暗处置零,以增强腐蚀的效果,方便简化稍后的运算和对边缘的判断。对于暗处,阈值的设置较为宽松(经过测试1~250之间都能得到较好的结果,不同的是,阈值较低,对边缘和细节的把控较好,误差也越低,实验中将其设置为16)。这是因为图像的背景条件非常好,并且霍夫变换仅仅选取一定斜率的线段,不对腐蚀进行增强也是可以的。
设置腐蚀度strelLlength = 8,这个值决定了求得的边缘的宽度。经过实验宽度设置为8效果最好。这个值与空泡区域拥有像素的多少有关。在实践中可以根据方法2中得到的结果动态优化。
将与原图像与腐蚀后图像相减,及可得到边缘。这是由于瓶子边缘比较亮,但是也最接近暗处,腐蚀时,首先被消除,因此可以得到较好的边缘。由于原图中在图像头部和底部自带一条白线,会造成一些特殊的形态,但这并不影响结果。

2. 求取肩部斜线段和是否装满判别标准线

使用霍夫变换,求得多条具有一定斜率的线段,在前条128线段中,选取一个角度在25~75度之间最长的线段,选取一个角度在-75 ~ -25之间最大长度的线段。这两条线段的中点求平均,以过这点的水平线为判别标准线。选取的前128条线段如下图霍夫曼空间中小方框所示,标准线获取结果如下如图所示:


3. 灰度图二值化

方法一:全局最佳阈Ostu算法+腐蚀和膨胀
方法二:设置阈值180,这也能得到一个较好的结果,如下图所示

4. 寻找连通区域

使用bwconncomp函数获取连通区域。同时我们需要修正连通区域,这是因为在图像加工中,我们主要是为了获得需要的信息。为了统计瓶子中空气区域(以下称为空泡区域)的大小,我们需要将这些空泡区域提取出来。这些空泡区域的一大特征是对光线吸收较少,亮度较高,因此我们可以根据灰度图中的像素的亮度来提取空泡区域。但由于瓶子和液体对光线具有折射和汇聚的作用,会在瓶子两侧边缘各形成一条突出的光带,光带的亮度和瓶子空泡区域的亮度相近,这是对液面分析的一大影响。除此之外,还有一些噪声,这些都是需要剔除的。我们去除连通区域小于200的连通区域。

5. 为连通区绘制方框

经过修正连通区域后,只剩下了7个连通区域,及5个瓶子的空气区域和上下2个光带。将他们的边框绘制出来,如下图所示。

6. 获取统计信息、判断是否未装满

判断连通区域像素点数是否大于4000,若大于则说明未装满,将其标识出来

7. 根据方框与判别标准线的位置判断液面是否位于判别标准线

由于腐蚀及其他因素的存在,可以设置误差等于腐蚀度strelLlength = 8。及标准线不得进入方框超过标准线8个像素的位置。将结果显示出来如下两图所示:


8. 综合判断

将两者结合,充分利用连通区域像素点数这个信息,既可以动态的优化方案一中的参数,也可以辅助方案一。并且,方案二具有记忆特性,可以根据历史信息来动态调整。将方案一和方案二结合,方案将更加健壮,因为这种设计结合了大量的数据和经验。

代码如下

img = imread("a.tif");
subplot(3,3,1)
imshow(img);
title('原图');
img = rgb2gray(img); %转为灰度图
[X,Y] = size(img); 

%% 灰度二值化
img1 = im2bw(img,180/255); 
img1 = bwareaopen(img1,200); %除去面积小于200的连通区域 
subplot(3,3,2);
imshow(img1);
title('去噪后二值化后图像');
%% 根据像亮泡面积判断是否为未装满
cc = bwconncomp(img1, 8);%求二值化后连通区域
s=regionprops(img1,'BoundingBox'); % 框
r=struct2cell(s);

numPixels = cellfun(@numel,cc.PixelIdxList);
subplot(3,3,3);
imshow(img1);
title('根据空泡面积判断是否装满');



for i = 1:length(numPixels)
    if numPixels(i) >= 4000 % 面积大于4000像素
        rectangle('position',r{i},'edgecolor','c','LineWidth',2);
        hold on;
    end
end

%% 根据双肩斜线中点水平线判断是否未装满
%% 增强腐蚀后与原图相减求得边缘
img2 = img;
meanImg = 16;
strelLlength = 8;

for x = 1:X
    for y = 1:Y
        if img2(x,y) <= meanImg
            img2(x,y) = 0;
        end
    end
end
se1=strel('disk',strelLlength);%创建一个半径为strelLlength的平坦型圆盘结构元素
img3 =imerode(img2,se1);
for x = 1:X
    for y = 1:Y
        if img3(x,y) >= meanImg
            img2(x,y) = 0;
        end
    end
end


img3 = img2;
subplot(3,3,4)
imshow(img2);
title('腐蚀取得边缘');

%% 霍夫变换求肩边斜线(选取最长的左肩和右肩斜线),取中点水平线为标准线
[H, theta, rho] = hough(img3);
subplot(3,3,5)
imshow(imadjust(mat2gray(H)),[],'XData',theta,'YData',rho,...
        'InitialMagnification','fit');
        xlabel('\theta (degrees)'), ylabel('\rho');
        axis on, axis normal, hold on;colormap(hot);title('霍夫空间')

%%峰值
frontK = 128;
P = houghpeaks(H,frontK,'threshold',0.1*max(H(:)));
x = theta(P(:,2));
y = rho(P(:,1));
plot(x,y,'s','color','black');

%前frontK条线段里找到前长度最长,角度位于dTheta~90-dTheta之间的线段
lines = houghlines(img3,theta,rho,P,'FillGap',4,'MinLength',10); 
subplot(3,3,6)
imshow(img3) ,hold on
dTheta = 25;
maxlengthL = 0;
maxlengthR = 0;
for k = 1:length(lines)
    if ((lines(k).theta>dTheta) && (lines(k).theta<90-dTheta))
          po1 = lines(k).point1;
          po2 = lines(k).point2;
          dis = sum((po1-po2).^2);
          if(dis > maxlengthL)
              lineL = lines(k);
              maxlengthL = dis;
          end
    elseif ((lines(k).theta>(dTheta-90)) && (lines(k).theta<(-dTheta)))
      po1 = lines(k).point1;
      po2 = lines(k).point2;
      dis = sum((po1-po2).^2);
      if(dis > maxlengthR)
          lineR = lines(k);
          maxlengthR = dis;
      end
    end
end

xy = [lineL.point1; lineL.point2];
plot(xy(:,1),xy(:,2),'LineWidth',2,'Color','green');
% Plot beginnings and ends of lines
plot(xy(1,1),xy(1,2),'x','LineWidth',2,'Color','yellow');
plot(xy(2,1),xy(2,2),'x','LineWidth',2,'Color','red');

xy = [lineR.point1; lineR.point2];
plot(xy(:,1),xy(:,2),'LineWidth',2,'Color','green');
% Plot beginnings and ends of lines
plot(xy(1,1),xy(1,2),'x','LineWidth',2,'Color','yellow');
plot(xy(2,1),xy(2,2),'x','LineWidth',2,'Color','red');

%画出标准线
standardX = floor((lineL.point1(2)+lineL.point2(2)+lineR.point1(2)+lineR.point2(2))/4);
plot([0:Y-1], standardX*ones(1,Y),'LineWidth',2,'Color','green');
title('斜肩检测,及标准线计算');




%% 根据标准线判断是否未装满,将结果画出
subplot(3,3,7)
imshow(img1)
cc = bwconncomp(img1, 8);%求二值化后连通区域
s=regionprops(img1,'BoundingBox'); % 框
r=struct2cell(s);

for i = 1:length(numPixels)
    rectangle('position',r{i},'edgecolor','c','LineWidth',2);
    hold on;
end
title('全部方框');

%设置误差a
a = strelLlength;
subplot(3,3,8);
imshow(img1);
subplot(3,3,9);
imshow(img);
for i = 1:length(r)  
    if (r{i}(2)<standardX-a) && (r{i}(2)+r{i}(4)>standardX+a)
        subplot(3,3,8);
        rectangle('position',r{i},'edgecolor','c','LineWidth',2);
        hold on;
        subplot(3,3,9);
        rectangle('position',r{i},'edgecolor','c','LineWidth',2);
        hold on;
    end
end

%画出标准线
subplot(3,3,8);
plot([0:Y-1], (standardX)*ones(1,Y),'LineWidth',2,'Color','red'), 
title('二值图中依据标准线检测和标识未装满瓶子');

subplot(3,3,9);
plot([0:Y-1], (standardX)*ones(1,Y),'LineWidth',2,'Color','red'), 
title('原图中依据标准线检测和标识未装满瓶子');
posted @ 2021-05-06 23:02  minLoong  阅读(1182)  评论(0)    收藏  举报