实验项目名称:图像分割实验
实验项目性质:验证性实验
所属课程名称:数字图像分析与艺术化处理
实验计划学时: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算子是最优的边缘检测算子,是基于一阶微分算子的基础上进行改进得到的。
- 实验内容和要求
- 图像的阈值分割:
图像为车牌图像,编写代码实现基本全局阈值法和Otsu法,输出阈值T,比较分割结果。
- 边缘检测
用边缘检测算子对车牌图像进行处理,可以用梯度算子、LOG算子或Canny算子(LOG算子可以用5*5的近似核,Canny算子可以直接用OpenCV函数)。比较先阈值分割后边缘检测和直接对灰度图像进行边缘检测这两种情况的结果是否有差别,有什么差别。
注意:这里提取灰度边缘即可。
核心代码:
// 基本全局阈值法
double basicGlobalThreshold(const Mat& src, double initialT, double T0) {
double T = initialT;
double prevT;
double u1=0, u2=0, G1=0, G2=0, n_G1=0, n_G2=0;
int cols, rows;
cols = src.cols;
rows = src.rows;
do {
prevT = T;
for(int i=0;i<rows;i++)
for (int j = 0; j < cols; j++) {
if (src.at<uchar>(i, j) > T) {
G1 += src.at<uchar>(i, j);
n_G1++;
}
else {
G2 += src.at<uchar>(i, j);
n_G2++;
}
}
u1 = G1 / n_G1;
u2 = G2 / n_G2;
T = (u1 + u2) / 2;
} while (abs(T - prevT) >= T0);
return T;
}
//otsuThreshold
double OtsuThreshold(const cv::Mat& src) {
// 计算直方图
std::vector<int> histogram(256, 0);
for (int y = 0; y < src.rows; y++) {
for (int x = 0; x < src.cols; x++) {
histogram[src.at<uchar>(y, x)]++;
}
}
// 归一化
int total = src.rows * src.cols;
std::vector<double> normalizedHist(256, 0);
for (int i = 0; i < 256; i++) {
normalizedHist[i] = (double)histogram[i] / total;
}
// 计算累积和与累积均值
std::vector<double> cumulativeSum(256, 0);
std::vector<double> cumulativeMean(256, 0);
cumulativeSum[0] = normalizedHist[0];
cumulativeMean[0] = 0; // 0 * normalizedHist[0] is always 0
for (int i = 1; i < 256; i++) {
cumulativeSum[i] = cumulativeSum[i - 1] + normalizedHist[i];
cumulativeMean[i] = cumulativeMean[i - 1] + i * normalizedHist[i];
}
double globalMean = cumulativeMean[255];
// 寻找最大类间方差
double maxVariance = 0;
int threshold = 0;
for (int i = 0; i < 256; i++) {
double w0 = cumulativeSum[i];
double w1 = 1 - w0;
if (w0 == 0 || w1 == 0) continue;
double u0 = cumulativeMean[i] / w0;
double u1 = (globalMean - cumulativeMean[i]) / w1;
double variance = w0 * w1 * (u0 - u1) * (u0 - u1);
if (variance > maxVariance) {
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
int main() {
Mat src = imread("C:\\Users\\23609\\OneDrive\\图片\\保存照片\\4BEEFDEFFE786B33C98E9A24242FC0F9.bmp", 0);
if (src.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
Mat basicThresh, otsuThresh, cannyEdges, cannyAfterBasicThresh, cannyAfterOtsuThresh;
double initialT = 127;
double T0 = 0.5;
double T_basic = basicGlobalThreshold(src, initialT, T0);
threshold(src, basicThresh, T_basic, 255, THRESH_BINARY);
double T_otsu = threshold(src, otsuThresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Canny(src, cannyEdges, 50, 150);
Canny(basicThresh, cannyAfterBasicThresh, 50, 150);
Canny(otsuThresh, cannyAfterOtsuThresh, 50, 150);
cout << "Basic Global Threshold: " << T_basic << endl;
cout << "Otsu Threshold: " << T_otsu << endl;
imshow("Original Image", src);
imshow("Basic Global Thresholding", basicThresh);
imshow("Otsu Thresholding", otsuThresh);
imshow("Canny Edges on Original", cannyEdges);
imshow("Canny Edges on BasicThresholded Image", cannyAfterBasicThresh);
imshow("Canny Edges on OtsuThresholded Image", cannyAfterOtsuThresh);
waitKey(0);
return 0;
}
两者阈值差别不大,otsu阈值总是更小一些
可以看出otsu算法由于阈值低保留下的前景更多
边缘检测对比发现,相比没有图像分割处理的检测,两种算法图像分割后保留下的边缘更细,但也会使得明暗变化不大地方的边缘有少量被忽略。而原始处理的边缘检测颜色很重很黑,线条很多,边缘之内的东西也被标记处大量细节,但是也因为此很多不明显的边缘被保留。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
// 基本全局阈值法
double basicGlobalThreshold(const Mat& src, double initialT, double T0) {
double T = initialT;
double prevT;
double u1=0, u2=0, G1=0, G2=0, n_G1=0, n_G2=0;
int cols, rows;
cols = src.cols;
rows = src.rows;
do {
prevT = T;
for(int i=0;i<rows;i++)
for (int j = 0; j < cols; j++) {
if (src.at<uchar>(i, j) > T) {
G1 += src.at<uchar>(i, j);
n_G1++;
}
else {
G2 += src.at<uchar>(i, j);
n_G2++;
}
}
u1 = G1 / n_G1;
u2 = G2 / n_G2;
T = (u1 + u2) / 2;
} while (abs(T - prevT) >= T0);
return T;
}
double OtsuThreshold(const cv::Mat& src) {
// 计算直方图
std::vector<int> histogram(256, 0);
for (int y = 0; y < src.rows; y++) {
for (int x = 0; x < src.cols; x++) {
histogram[src.at<uchar>(y, x)]++;
}
}
// 归一化
int total = src.rows * src.cols;
std::vector<double> normalizedHist(256, 0);
for (int i = 0; i < 256; i++) {
normalizedHist[i] = (double)histogram[i] / total;
}
// 计算累积和与累积均值
std::vector<double> cumulativeSum(256, 0);
std::vector<double> cumulativeMean(256, 0);
cumulativeSum[0] = normalizedHist[0];
cumulativeMean[0] = 0; // 0 * normalizedHist[0] is always 0
for (int i = 1; i < 256; i++) {
cumulativeSum[i] = cumulativeSum[i - 1] + normalizedHist[i];
cumulativeMean[i] = cumulativeMean[i - 1] + i * normalizedHist[i];
}
double globalMean = cumulativeMean[255];
// 寻找最大类间方差
double maxVariance = 0;
int threshold = 0;
for (int i = 0; i < 256; i++) {
double w0 = cumulativeSum[i];
double w1 = 1 - w0;
if (w0 == 0 || w1 == 0) continue;
double u0 = cumulativeMean[i] / w0;
double u1 = (globalMean - cumulativeMean[i]) / w1;
double variance = w0 * w1 * (u0 - u1) * (u0 - u1);
if (variance > maxVariance) {
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
int main() {
Mat src = imread("C:\\Users\\23609\\OneDrive\\图片\\保存照片\\4BEEFDEFFE786B33C98E9A24242FC0F9.bmp", 0);
if (src.empty()) {
cout << "Could not open or find the image" << endl;
return -1;
}
Mat basicThresh, otsuThresh, cannyEdges, cannyAfterBasicThresh, cannyAfterOtsuThresh;
double initialT = 127;
double T0 = 0.5;
double T_basic = basicGlobalThreshold(src, initialT, T0);
threshold(src, basicThresh, T_basic, 255, THRESH_BINARY);
double T_otsu = threshold(src, otsuThresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Canny(src, cannyEdges, 50, 150);
Canny(basicThresh, cannyAfterBasicThresh, 50, 150);
Canny(otsuThresh, cannyAfterOtsuThresh, 50, 150);
cout << "Basic Global Threshold: " << T_basic << endl;
cout << "Otsu Threshold: " << T_otsu << endl;
imshow("Original Image", src);
imshow("Basic Global Thresholding", basicThresh);
imshow("Otsu Thresholding", otsuThresh);
imshow("Canny Edges on Original", cannyEdges);
imshow("Canny Edges on BasicThresholded Image", cannyAfterBasicThresh);
imshow("Canny Edges on OtsuThresholded Image", cannyAfterOtsuThresh);
waitKey(0);
return 0;
}