Ex5:图像分割

计算机视觉作业五

完整代码见github: https://github.com/linjiafengyang/ComputerVision/tree/master/Ex5

迭代法求阈值

步骤如下:

  1. 计算灰度图的直方图分布
  2. 阈值threshold初始化为总灰度和的平均值
  3. 计算小于等于阈值threshold的灰度平均值t1
  4. 计算大于阈值threshold的灰度平均值t2
  5. 计算新阈值threshold_new = (t1 + t2) / 2
  6. 比较两个阈值,若两个阈值相等,则返回阈值threshold,否则更新阈值从第3步继续循环

关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 求阈值
int Iteration::iteration(const CImg<float>& image) {
// 灰度直方图初始化为0
for (int i = 0; i < 256; i++) {
histogram[i] = 0;
}
pixelsNum = image.width() * image.height();
// 计算灰度直方图分布,Histogram数组下标是灰度值,保存内容是灰度值对应像素点数
cimg_forXY(image, i, j) {
++histogram[int(image(i, j, 0))];
}

threshold = 0;
for (int i = 0; i < 256; i++) {
threshold += i * histogram[i];
}
threshold /= pixelsNum; // 阈值初始化为总灰度和的平均值

int threshold_new;
while (1) {
int t1 = 0, t2 = 0;
int num1 = 0, num2 = 0;

// 计算小于等于阈值threshold的灰度平均值t1
for (int i = 0; i <= threshold; i++) {
t1 += i * histogram[i];
num1 += histogram[i];
}
if (num1 == 0) continue;
t1 /= num1;

// 计算大于阈值threshold的灰度平均值t2
for (int i = threshold + 1; i < 256; i++) {
t2 += i * histogram[i];
num2 += histogram[i];
}
if (num2 == 0) continue;
t2 /= num2;

threshold_new = (t1 + t2) / 2;
// 若两个阈值相等,则返回阈值threshold,否则更新阈值继续循环
if (threshold == threshold_new) break;
else threshold = threshold_new;
}
return threshold;
}

OSTU法求阈值

步骤如下

  1. 初始化一些参数,比如像素点总数pixelsNum等,然后计算灰度图的直方图分布
  2. 从0到255开始循环:计算前景像素点总数和前景像素总灰度和
  3. 计算前景像素平均灰度m1(前景像素点总数/前景像素总灰度和)和前景像素点数所占比例P1(前景像素点总数/像素点总数pixelsNum
  4. 计算背景像素点总数和背景像素总灰度和
  5. 计算背景像素平均灰度m2(背景像素点总数/背景像素总灰度和)和背景像素点数所占比例P2(背景像素点总数/像素点总数pixelsNum
  6. 计算当前类间方差temp_variance = P1 * P2 * (m1 - m2) * (m1 - m2),作比较更新类间方差和阈值

关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// ostu算法求阈值
int OSTU::ostu(const CImg<float>& image) {
variance = 0; // 类间方差初始化为0
// 灰度直方图初始化为0
for (int i = 0; i < 256; i++) {
histogram[i] = 0;
}
pixelsNum = image.width() * image.height(); // 像素点总数
// 计算灰度直方图分布,Histogram数组下标是灰度值,保存内容是灰度值对应像素点数
cimg_forXY(image, i, j) {
++histogram[int(image(i, j, 0))];
}

for (int i = 0; i < 256; i++) {
P1 = 0; P2 = 0; m1 = 0; m2 = 0;
for (int j = 0; j <= i; j++) {
P1 += histogram[j]; // 前景像素点总数
m1 += j * histogram[j]; // 前景部分像素总灰度和
}
if (P1 == 0) continue;
m1 /= P1; // 前景像素平均灰度
P1 /= pixelsNum; // 前景像素点数所占比例

for (int j = i + 1; j < 256; j++) {
P2 += histogram[j]; // 背景像素点总数
m2 += j * histogram[j]; // 背景部分像素总灰度和
}
if (P2 == 0) continue;
m2 /= P2; // 背景像素平均灰度
P2 /= pixelsNum; // 背景像素点数所占比例

double temp_variance = P1 * P2 * (m1 - m2) * (m1 - m2); // 当前类间方差
// 更新类间方差和阈值
if (variance < temp_variance) {
variance = temp_variance;
threshold = i;
}
}
return threshold;
}

图像分割结果

下面三张图左为原图右为分割结果图:
这里写图片描述

这里写图片描述

这里写图片描述

结果分析

其实,这两种算法都有一定的局限性。造成结果好坏的原因主要有:

  1. A4纸本身褶皱,导致有阴影形成,利用阈值法进行图像分割时会形成噪声,会被误认为是背景。
  2. 背景和前景区分不明显,即是说A4纸边缘和背景没有明显界面,如背景和A4纸一样偏白色时,此时做图像分割会非常困难。
  3. 拍照时光线也会对图像分割形成一定干扰。
  4. 算法本身的不足也对结果形成一定的影响。因为算法考虑的是全局阈值,全局阈值时针对整张图像而言,例如A4纸中的阴影(光线不足或者遮挡形成)会被误认为是小于等于阈值的背景。
  5. 根据上述几点,可以得出分割结果好的拍摄要求大体是:A4纸无折痕,无褶皱,投射到A4纸的光线要充足且均匀,即亮度要平均,不能在A4纸上形成某一块阴影,与此同时应尽量使得背景和前景区分明显,才能做一个效果比较好的图像分割。
------本文结束感谢阅读------