实验三 图像分割实验
实验项目名称:图像分割实验
实验项目性质:验证性实验
所属课程名称:数字图像分析与艺术化处理
实验计划学时:2
一、实验目的
- 进一步理解图像的阈值分割方法和边缘检测方法的原理。
- 掌握图像基本全局阈值方法和最大类间方差法(otsu法)的原理并编程实现。
- 编程实现图像的边缘检测。
二、实验内容和要求
编程实现图像阈值分割(基本全局阈值方法和otsu法)和边缘检测。
三、实验主要仪器设备和材料
计算机,VS2017+OpenCV
四、实验原理与方法
1、图像的阈值分割的基本原理
图像的二值化处理图像分割中的一个主要内容,就是将图像上的点的灰度置为0或255,也就是讲整个图像呈现出明显的黑白效果。用I表示原图,R表示二值化后的图,则二值化的过程可以用以下公式表示:
编辑
thr表示选取的阈值。二值化的过程就是当原图的像素灰度值大于阈值就将其变白,否则就将其变黑。即将256个亮度等级的灰度图像通过适当的阀值选取而将图像变为二个级别灰度级,这样只有二个灰度级的图像在图像处理分析过程中占有非常重要的地位,特别是在实用的图像处理中。
根据对全图使用统一阈值还是对不同区域使用不同阈值,可以分为全局阈值方法(global thresholding)和局部阈值方法(local thresholding,也叫做自适应阈值方法adaptive thresholding);这种与坐标相关的阈值也叫动态阈值,具体的方法,可以参考相关的图像处理书籍。
基本全局阈值方法,即在整个图像中所有的象素点,其阈值thr相同,具体步骤为:
- 选取一个初始估计值T;
- 用T分割图像。这样便会生成两组像素集合:G1由所有灰度值大于T的像素组成,而G2由所有灰度值小于或等于T的像素组成。
- 对G1和G2中所有像素计算平均灰度值u1和u2。
- 计算新的阈值:T=(u1 + u2)/2。
- 重复步骤(2)到(4),直到得到的T值之差小于一个事先定义的参数T0。
Otsu方法的算法步骤为:
- 先计算图像的归一化直方图;
- i表示分类的阈值,也即一个灰度级,从0开始迭代;
- 通过归一化的直方图,统计0~i 灰度级的像素(背景像素) 所占整幅图像的比例w0,并统计背景像素的平均灰度u0;统计i~255灰度级的像素(前景像素) 所占整幅图像的比例w1,并统计前景像素的平均灰度u1;
- 计算前景像素和背景像素的方差 g = w0*w1*(u0-u1) (u0-u1)
- i++,直到i为256时结束迭代;
- 将最大g相应的i值作为图像的全局阈值。
2、边缘检测
图像中边缘的检测可以借助一阶和二阶微分实现,常见的一阶边缘检测算子即梯度算子包括Roberts算子、Prewitt算子和Sobel算子,二阶算子主要是Laplacian算子,由于受噪声影响比较大,往往在使用之前先对图像进行平滑处理,LOG算子就是先对图像进行高斯平滑,然后进行拉普拉斯变换并求零交叉点。Canny算子是最优的边缘检测算子。
五、实验内容和要求
1.图像的阈值分割:
图像为车牌图像,编写代码实现基本全局阈值法和Otsu法,比较分割结果。
2.边缘检测
用边缘检测算子对车牌图像进行处理,可以用梯度算子、LOG算子或Canny算子(Canny算子可以直接用OpenCV函数)。比较先阈值分割后边缘检测和直接对图像进行边缘检测这两种情况的结果是否有差别。
注意:这里提取灰度边缘即可。
六.实验代码
#include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; // 拉普拉斯锐化函数 void LaplacianSharpDeal(const Mat& src, Mat& dst) { if (!src.data)return; for (int i = 0; i < src.rows; ++i) for (int j = 0; j < src.cols; ++j) { float a; if (i > 1 && i < src.rows - 1 && j > 1 && j < src.cols - 1) { a = 5 * (float)src.at<uchar>(i, j) - (float)src.at<uchar>(i - 1, j) - (float)src.at<uchar>(i, j - 1) - (float)src.at<uchar>(i, j + 1) - (float)src.at<uchar>(i + 1, j); } else {//边缘赋值 a = src.at<uchar>(i, j); } if (a > 255 || a < 0) { dst.at<uchar>(i, j) = src.at<uchar>(i, j); } else { dst.at<uchar>(i, j) = a; } } } // 基本全局阈值方法函数 int BasicGlobalThreshold(Mat src, float oldValue) { int cols = src.cols; int rows = src.rows; float G1 = 0; float G2 = 0; float g1 = 0; float g2 = 0; float u1 = 0; float u2 = 0; float T0 = 0; // 计算灰度直方图分布,统计像素数和频率 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (src.at<uchar>(i, j) > oldValue) { G1 += src.at<uchar>(i, j); g1 += 1; } else { G2 += src.at<uchar>(i, j); g2 += 1; } } } u1 = G1 / g1; u2 = G2 / g2; T0 = (u1 + u2) / 2; std::cout << T0 << std::endl; if (abs(oldValue - T0) < 0.1) { return T0; } else { BasicGlobalThreshold(src, T0); } } // Otsu方法函数 int Otsu(Mat src) { int cols = src.cols; int rows = src.rows; int nPixelNum = cols * rows; // 初始化 int pixelNum[256]; double probability[256]; for (int i = 0; i < 256; i++) { pixelNum[i] = 0; probability[i] = 0.0; } // 统计像素数和频率 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { pixelNum[src.at<uchar>(i, j)]++; } } for (int i = 0; i < 256; i++) { probability[i] = (double)0.1 * pixelNum[i] / nPixelNum; } // 计算 int Threshold = 0; // 最佳阈值 double MaxDelta = 0.0; // 最大类间方差 double Mean_0 = 0.0; // 左边平均值 double Mean_1 = 0.0; // 右边平均值 double Delta = 0.0; // 类间方差 double Mean_0_temp = 0.0; // 左边平均值中间值 double Mean_1_temp = 0.0; // 右边平均值中间值 double Probability_0 = 0.0; // 左边频率值 double Probability_1 = 0.0; // 右边频率值 for (int j = 0; j < 256; j++) { for (int i = 0; i < 256; i++) { if (i < j)// 前半部分 { Probability_0 += probability[i]; Mean_0_temp += i * probability[i]; } else // 后半部分 { Probability_1 += probability[i]; Mean_1_temp += i * probability[i]; } } // 计算平均值 // Mean_0_teamp计算的是前半部分的灰度值的总和除以总像素数, // 所以要除以前半部分的频率才是前半部分的平均值,后半部分同样 Mean_0 = Mean_0_temp / Probability_0; Mean_1 = Mean_1_temp / Probability_1; Delta = (double)(Probability_0 * Probability_1 * pow((Mean_0 - Mean_1), 2)); if (Delta > MaxDelta) { MaxDelta = Delta; Threshold = j; } // 相关参数归零 Probability_0 = 0.0; Probability_1 = 0.0; Mean_0_temp = 0.0; Mean_1_temp = 0.0; Mean_0 = 0.0; Mean_1 = 0.0; Delta = 0.0; } return Threshold; } void main() { Mat image = imread("A1.bmp", 0); Mat image1, image2; Mat image3(image.size(), image.type()); Mat image4(image.size(), image.type()); std::cout << "基本全局阈值方法" << std::endl; int OstuThreshold1 = BasicGlobalThreshold(image, 0.01); int OstuThreshold2 = Otsu(image); std::cout << "Otsu方法" << std::endl; std::cout << OstuThreshold2 << std::endl; threshold(image, image1, OstuThreshold1, 255, THRESH_OTSU); threshold(image, image2, OstuThreshold2, 255, THRESH_OTSU); LaplacianSharpDeal(image2, image3); LaplacianSharpDeal(image, image4); imshow("原图像二值化", image); imshow("基本全局阈值方法", image1); imshow("Otsu方法", image2); imshow("先阈值分割后边缘检测", image3); imshow("直接对图像进行边缘检测", image4); waitKey(); }
七、实验结果
(1)
编辑
(2)
编辑
在本图中,很明显先阈值分割后边缘检测的效果比直接进行边缘检测效果好,但阈值分割效果不是很明显,但仔细比较Otsu方法的分割要优于基本的全局阈值处理,采用基本的全局阈值处理得到的图像分割效果没有Otsu效果好,这是因为阈值不能通过迭代很好的确定导致的。本次实验中,学习到了阈值分割以及边缘检测的方法,对图像进行实践,能更好的学习应用当中。