图像处理(二)

前言

上节内容学习了图像的基本处理方法, 这一节学习一些图像处理上稍微进阶一点的知识. 关于图像处理的知识体系庞大, 需要以后投入很大的精力去研究学习, 这也是我未来学习研究的重点之处.

对于下面的这张rice.png的灰度图像, 如何能够获得关于图像的描述信息 , 也是图像处理的一个很重要的方面. 比如说, 如何求出这张图片中有多少粒米? 这些米中哪个是最大的? 平均大小有又是多少?

image-20200713144402371

在数字图像处理那门课中, 曾经学习过关于图像像素点之间的位置关系, 比如 4连接等等. 那里所提到的位置关系的判断都是基于二值图像的, 所以这里想要使用那里提供的思路的话, 第一步要进行图像的二值化处理. 对图像二值化处理以后, 就可以可以利用图像像素点之间的位置关系来判断哪些像素点的是米, 哪些像素点是背景. 米的像素点都是偏亮的, 因此可以从上到下, 从左到右遍历整个图像, 遇到偏亮的像素点对其进行标记, 并对其相互连接的像素点做同样的标记, 这样就可以标记处整个图像中全部的米.

图像二值化

图像二值化的关键在于如何选取二值化处理的阈值, 对于灰度值在阈值以上的像素点将其灰度值置为1, 灰度值在阈值一下的像素点将其灰度值设为0. 关于如何选取阈值, 涉及了很多数学相关的计算, 不是一个简单的问题. matlab中提供了内置函数graythresh来找到一幅图像的阈值. 能够成功获得阈值, 那后面的二值化处理就变得很简单了, 只需要遍历整个矩阵, 依次和阈值进行比较即可. 这个操作matlab中也提供了内置函数来完成, im2bw. (bw 就是black 和white的意思)

于是, 图像二值化问题就迎刃而解, 参考如下代码.

1
2
3
4
I = imread('rice.png'); level=graythresh(I);
bw=im2bw(I, level); % 第一个参数是处理的对象 第二个参数是二值化处理的阈值
subplot(1,2,1); imshow(I);
subplot (1,2,2); imshow(bw);

执行结果如图:

image-20200713150927858


练习: 不使im2bw函数 来实现图像的二值化处理. 尝试使用不同的阈值分别会是什么效果.

1
2
3
4
5
6
7
8
9
10
function J=im2bw_m(I,x)
for ii=1:size(I,1)
for jj=1:size(I,2)
if I(ii,jj)>x
J(ii,jj)=1;
else
J(ii,jj)=0;
end
end
end

测试代码:

1
2
3
4
5
6
7
I = imread('rice.png');
x = mean(mean(I)); % 这里使用平均值作为阈值
J = im2bw_m(I,x);
K = im2bw(I,graythresh(I));
subplot(1,3,1); imshow(I);
subplot(1,3,2); imshow(J);
subplot(1,3,3); imshow(K);

image-20200713152255074

结果一目了然, 我们的处理效果太差了, 阈值绝不是平均值这么简单.

背景估计

内置函数处理的效果虽然很好, 但是还有瑕疵, 二值处理后的图像存在许多"噪声点", 之前介绍的米粒数目计算的算法会把这些小白点都当成米来计算, 因此这些小白点会直接影响我们的米粒数目计算算法得出的结果, 从这个角度看, 这可能不在是一个小瑕疵了.

产生小白点的原因是 由于原图中光线角度的问题, 图像的上半部整体亮度高于下半部, 在二值化过程中 由于 上面的背景像素过亮而被处理成1, 下半部分的米粒像素偏暗而被当成背景处理成0. 可以看到内置的二值化函数的处理结果中上半部分有许多噪声点, 下半部分又有许多米粒没有显示出来.

image-20200713153333903

对于这一问题, matlab中提供了内置函数imopen来预测图像的背景, 这个函数的背后原理比较复杂, 这里不对其进行介绍, 只给出使用示例.

1
2
3
I = imread('rice.png');
BG = imopen(I, strel('disk', 15));
imshow(BG);

image-20200713154442306

可以看到, 图像的上半部分和下半部分提取的背景灰度值存在着明显的差异.

有了背景图像, 就可以通过上一篇文章中介绍的图像做差的方式得到一个米粒明显的图像.

1
2
3
4
5
6
I = imread('rice.png');
subplot(1,3,1); imshow(I);
BG = imopen(I, strel('disk', 15));
subplot(1,3,2); imshow(BG);
I2 = imsubtract(I, BG); % 图像做差
subplot(1,3,3); imshow(I2);

image-20200713155004968

进而可以对做差后的图像进行二值化处理, 得到一个比较优秀的二值图像.

1
2
3
4
5
6
7
8
I = imread('rice.png'); level=graythresh(I);
bw = im2bw(I, level); subplot (1,2,1);

imshow(bw); BG = imopen(I, strel('disk', 15));
I2 = imsubtract(I, BG); % 图像做差
level=graythresh(I2);
bw2 = im2bw(I2, level);
subplot(1,2,2); imshow(bw2);

对比效果:

image-20200713155149673


标记矩阵

现在我们有了一幅比较完美的二值图像, 就该进入到查米粒个数的步骤了. 在文章开头已经介绍了大致的算法, 这里介绍具体的实现方法.

我们目前的图像由0 和 1 组成, 为了标记出哪些像素点是米粒, 哪些像素点是背景, 还需要引入一个等大的矩阵专门用来标记, 称为标记矩阵.

  1. 开始, 初始化标记 flag = 1, 等大的标记矩阵全为0.
  2. 从上到下, 从左到右扫描矩阵.判断像素点的灰度值
  3. 如果是0, 则继续扫描.
  4. 如果是1, 且标记矩阵为对应位置是0 (未被标记过), 对标记矩阵对应位置标记为 flag , 接下来对原图像该点的右邻接点和下邻接点进行判断
    • 如果是1, 重复执行步骤4.
    • 如果是0, 局部终止.
  5. 全部标记完1粒米后, flag = flag+1; 转到步骤2.
  6. 扫描全部图像后, flag的数值就是米粒的个数.

下面给出步骤图解, 便于理解:

  1. 初始化: image-20200713162649003
  2. image-20200713162716040
  3. image-20200713162743364
  4. image-20200713162808303
  5. image-20200713162827031
  6. image-20200713162852094

上述算法在matlab中也提供了内置函数,bwlabel , 从名字中可以看出, 此函数只能用于黑白二值图像中. 下面给出使用示例.

1
2
3
4
5
I=imread('rice.png');
BG=imopen(I, strel('disk', 15));
I2=imsubtract(I, BG); level=graythresh(I2);
BW=im2bw(I2, level);
[labeled, numObjects]=bwlabel(BW, 8);

可以看到, 返回矩阵labeled 中, 实现了对米粒位置的标记. numObjects 返回的数值为99, 也就是整幅图像的米粒个数.

image-20200713163647424

进阶处理

使用label2rgb函数实现对标记矩阵彩色处理.

1
2
3
4
5
6
I=imread('rice.png');
BG=imopen(I, strel('disk', 15));
I2=imsubtract(I, BG); level=graythresh(I2);
BW=im2bw(I2, level);
[labeled, numObjects]=bwlabel(BW, 8);
RGB_label=label2rgb(labeled); imshow(RGB_label);

效果图:

image-20200713164316553


练习: 画出关于米粒大小的直方图; 把米粒颜色改为红色.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%% 计算米粒大小
function H = find_m(I,x)
H = zeros(x);
for ii=1:size(I,1)
for jj=1:size(I,2)
if I(ii,jj)~=0
H(I(ii,jj)) = H(I(ii,jj))+1;
end
end
end
%% 测试并绘图
I = imread('rice.png');
BG = imopen(I, strel('disk',15));
I1 = imsubtract(I,BG);
J = im2bw(I1,graythresh(I1));
[K,x] = bwlabel(J,8);
H = find_m(K,x);
histogram(H,25);

绘图结果:

image-20200713171413093


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%% RGB图像相当于三个层面的灰度图像, 其处理方式与灰度图像的处理方式区别不是很大.
function R = bw2r(I)
for ii=1:size(I,1)
for jj=1:size(I,2)
if I(ii,jj)~=0
R(ii,jj)=255;
end
end
end
%% 测试代码
I = imread('rice.png');
BG = imopen(I, strel('disk',15));
I1 = imsubtract(I,BG);
J = im2bw(I1,graythresh(I1));
R = bw2r(J);
G = zeros(size(I,1),size(I,2));
B = zeros(size(I,1),size(I,2));
RGB = cat(3,R,G,B); % 把三个颜色矩阵在第三维上连接起来
imshow(RGB);

显示结果:

image-20200713172456554


下面再介绍两个matlab的内置函数, regionpropsbwselect

regionprops可以返回图像区域的一些属性.

示例:

1
2
3
4
5
6
7
I=imread('rice.png');
BG=imopen(I, strel('disk', 15));
I2=imsubtract(I, BG); level=graythresh(I2);
BW=im2bw(I2, level);
[labeled, numObjects]=bwlabel(BW, 8);
graindata = regionprops(labeled, 'basic');
graindata(51)

结果为:

image-20200713173636700

可以看到, graindata是一个结构体类型的向量, 其中包含 面积, 中心点, BoundingBox 三个字段.上述代码输出的是第51个元素的属性.

image-20200713173932388

面积和中心点很容易理解, BoundingBox是指该图像中可以包含元素的最小方框的左上点坐标和右下点坐标.比如下图中的红色方框就是一个BoundingBox.

image-20200713174443961

bwselect函数可以实现用鼠标人工选择我们想要的元素.

示例:

1
2
3
4
I=imread('rice.png'); level=graythresh(I);
BG=imopen(I, strel('disk', 15));
I2=imsubtract(I, BG); BW=im2bw(I2, graythresh(I2));
ObjI = bwselect(BW); imshow(ObjI);

执行代码后会弹出一个图形, 把鼠标放在图像的米粒上面可以看到鼠标变成十字形, 点击即可选中, 按Esc键结束选择, imshow函数会显示出我们选择的图像.

image-20200713175126787


最后

从上面的直方图中可以看出, 有些米粒的大小特别小, 而有些却又特别大, 原因在于图像边缘部分有些米粒只有一部分在图像中 , 因此其大小比较小; 而有些米粒存在着相互连接的情况, 在标记过程中误把两粒米当成一粒米来处理, 导致米粒的大小过大. 因此, 本节的问题还没有结束, 图像处理道阻且长.

资料链接

参考视频 参考讲义