(1)进一步理解图像的阈值分割方法和边缘检测方法的原理。
(2)掌握图像基本全局阈值方法和最大类间方差法(otsu法)的原理并编程实现。
(3)编程实现图像的边缘检测。
编程实现图像阈值分割(基本全局阈值方法和otsu法)和边缘检测。
计算机,VS+OpenCV
图像的二值化处理图像分割中的一个主要内容,就是将图像上的点的灰度置为0或255,也就是讲整个图像呈现出明显的黑白效果。用I表示原图,R表示二值化后的图,则二值化的过程可以用以下公式表示:
thr表示选取的阈值。二值化的过程就是当原图的像素灰度值大于阈值就将其变白,否则就将其变黑。即将256个亮度等级的灰度图像通过适当的阀值选取而将图像变为二个级别灰度级,这样只有二个灰度级的图像在图像处理分析过程中占有非常重要的地位,特别是在实用的图像处理中。
根据对全图使用统一阈值还是对不同区域使用不同阈值,可以分为全局阈值方法(global thresholding)和局部阈值方法(local thresholding,也叫做自适应阈值方法adaptive thresholding);这种与坐标相关的阈值也叫动态阈值,具体的方法,可以参考相关的图像处理书籍。
基本全局阈值方法,即在整个图像中所有的象素点,其阈值thr相同,具体步骤为:
(1)选取一个初始估计值T;
(2)用T分割图像。这样便会生成两组像素集合:G1由所有灰度值大于T的像素组成,而G2由所有灰度值小于或等于T的像素组成。
(3)对G1和G2中所有像素计算平均灰度值u1和u2。
(4)计算新的阈值:T=(u1 + u2)/2。
(5)重复步骤(2)到(4),直到得到的T值之差小于一个事先定义的参数T0。
Otsu方法的算法步骤为:
(1)先计算图像的归一化直方图;
(2)i表示分类的阈值,也即一个灰度级,从0开始迭代;
(3)通过归一化的直方图,统计0~i 灰度级的像素(背景像素) 所占整幅图像的比例w0,并统计背景像素的平均灰度u0;统计i~255灰度级的像素(前景像素) 所占整幅图像的比例w1,并统计前景像素的平均灰度u1;
(4)计算前景像素和背景像素的方差 g = w0w1(u0-u1) (u0-u1)
(5)i++,直到i为256时结束迭代;
(6)将最大g相应的i值作为图像的全局阈值。
图像中边缘的检测可以借助一阶和二阶微分实现,常见的一阶边缘检测算子包括Roberts算子、Prewitt算子和Sobel算子,二阶算子主要是Laplacian算子,由于受噪声影响比较大,往往在使用之前先对图像进行平滑处理,LOG算子就是先对图像进行高斯平滑,然后进行拉普拉斯变换并求零交叉点。Canny算子是最优的边缘检测算子。
图像为车牌图像,编写代码实现基本全局阈值法和Otsu法,比较分割结果。
// 基本全局阈值法
int BasicGlobalThreshold(Mat src, float startT)
{
int cols = src.cols;
int rows = src.rows;
float newT = 0;
float T0 = 0.001;
float G1 = 0;
float G2 = 0;
float G1sum = 0;
float G2sum = 0;
int tag = 0;
// 计算值然后求平均,直到小于某个差值
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (src.at<uchar>(i, j) > startT)
{
G1 += src.at<uchar>(i, j);
G1sum += 1;
}
else
{
G2 += src.at<uchar>(i, j);
G2sum += 1;
}
}
}
float u1 = G1 / G1sum;
float u2 = G2 / G2sum;
newT = (u1 + u2) / 2;
cout << newT << endl;
tag++;
if (abs(startT - newT) < T0) {
return newT;
}
else
{
return BasicGlobalThreshold(src, newT);
}
}
使用大津法前需要进行直方图归一化
Mat srcNormalization(src.size(), src.type());
normalize(src, srcNormalization, 0,255, cv::NORM_MINMAX);
imshow("归一化直方图", srcNormalization);
// otsu大津法
int Otsu(Mat src) {
const int Graylevel = 256;
int graynum[Graylevel] = {
0 };
int rows = src.rows;
int cols = src.cols;
int thresh;
double everyLevelP[Graylevel] = {
0 };
double PixNum = rows * cols;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
//灰度像素统计
graynum[src.at<uchar>(i, j)]++;
//everyLevelP[src.at(i, j)] = graynum[i] / PixNum; //每个灰度级出现的比例
}
}
int gMaxIndex= 0;
double g[Graylevel] = {
0 };
double w0[Graylevel] = {
0 };
double w1[Graylevel] = {
0 };
double u0[Graylevel] = {
0 };
double u1[Graylevel] = {
0 };
int n0[Graylevel] = {
0 };
int n1[Graylevel] = {
0 };
for (int i = 0; i < Graylevel; i++) {
for (int j = 0; j < i; j++) {
n0[i] += graynum[j]; //前i个灰度级数量之和
u0[i] += j * graynum[j]; //前i个灰度级灰度的累加值
}
for (int j = i; j < Graylevel; j++) {
n1[i] += graynum[j]; //后i个灰度级数量之和
u1[i] += j * graynum[j]; //后i个灰度级灰度的累加值
}
//分母为零直接跳过,否则出现第一值为0,nan,后面计算最大方差比较的时候出问题。
if (n0[i] == 0|| n1[i] == 0) {
continue;
}
w0[i] = n0[i] / PixNum;
u0[i] = u0[i] / n0[i];
w1[i] = n1[i] / PixNum;
u1[i] = u1[i] / n1[i];
g[i] = w0[i] * w1[i] * (u0[i] - u1[i])*(u0[i] - u1[i]);
if (g[i] > g[gMaxIndex]) {
gMaxIndex = i;
//cout << gMaxIndex << endl;
}
}
return gMaxIndex;
}
//阈值处理
Mat thresholding(Mat src,float thresholdValue) {
Mat output(src.size(), src.type());
for (int i = 0; i < output.rows; i++){
for (int j = 0; j < output.cols; j++) {
if (int(src.at<uchar>(i, j)) < thresholdValue) {
output.at<uchar>(i, j) = 0;
}
else {
output.at<uchar>(i, j) = 255;
}
}
}
return output;
}
用边缘检测算子对车牌图像进行处理,可以用梯度算子、Laplacian算子或Canny算子(Canny算子可以直接用OpenCV函数)。比较先阈值分割后边缘检测和直接对图像进行边缘检测这两种情况的结果是否有差别。
注意:这里提取灰度边缘即可。
Mat cannyDirectEdge(src.size(), src.type());
Canny(src, cannyDirectEdge, 50, 150, 3);
//Laplacian(ostuThresholdImg, cannyEdge,8);
imshow("canny算子直接边缘检测", cannyDirectEdge);
Mat canny2ValueEdge(src.size(), src.type());
Canny(ostuThresholdImg, canny2ValueEdge, 50, 150, 3);
//Laplacian(ostuThresholdImg, cannyEdge,8);
imshow("canny算子先阈值分割再边缘检测", canny2ValueEdge);