图像二值化
二值图像,图像中只有两种颜色的信息,通常是黑色和白色,是将普通图像二值化后得到的图像 。图像二值化的作用是为了方便提取图像中的信
息。二值图像在进行计算机识别时可以增加识别效率。
比如 需要计算水面悬浮物的数量 就可以将一定面积的水拍成图片后二值化:
黑色为水 白色为悬浮物
然后通过计算机进行图像扫描
如果是黑色 0 就继续扫描
如果是白色 1就改变变量 通过连续算法 得出一个悬浮物
二值化就是就是将一幅图像的所有像素点按照256灰阶分类,每个像素点表示一个灰阶,然后我们将高于某一灰阶像素(阈值)全部显示成白色,低于某一灰阶(阈值)的像素点显示成黑色。这样就完成了对一幅图像二值化处理。
根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值。 比较常用的二值化方法则有:双峰法、P参数法、迭代法和OTSU法等。
OTSU法(大津法)
对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
ω0=N0/ M×N (1)
ω1=N1/ M×N (2)
N0+N1=M×N (3)
ω0+ω1=1 (4)
μ=ω0*μ0+ω1*μ1 (5)
g=ω0(μ0-μ)^2+ω1(μ1-μ)^2 (6)
将式(5)代入式(6),得到等价公式:
g=ω0ω1(μ0-μ1)^2 (7) 这就是类间方差
采用遍历的方法得到使类间方差g最大的阈值T,即为所求。(遍历灰度级(0-255),得到是的g取最大值的灰度级就是阈值T。)
大津法求阈值代码:
//大津法求阈值
int otsu(const IplImage *src_image)
{
double sum = 0.0;
double w0 = 0.0;
double w1 = 0.0;
double u0_temp = 0.0;
double u1_temp = 0.0;
double u0 = 0.0;
double u1 = 0.0;
double delta_temp = 0.0;
double delta_max = 0.0;
//src_image灰度级
int pixel_count[256] = { 0 };
float pixel_pro[256] = { 0 };
int threshold = 0;
uchar* data = (uchar*)src_image->imageData;//src->imageData是指向一片数据区的地址
//统计每个灰度级中像素的个数
for (int i = 0; i < src_image->height; i++)
{
for (int j = 0; j < src_image->width; j++)
{
pixel_count[(int)data[i * src_image->width + j]]++;//每个灰度级的像素数目
sum += (int)data[i * src_image->width + j];//灰度之和
}
}
cout << "平均灰度:" << sum / (src_image->height * src_image->width) << endl;
//计算每个灰度级的像素数目占整幅图像的比例
for (int i = 0; i < 256; i++)
{
pixel_pro[i] = (float)pixel_count[i] / (src_image->height * src_image->width);
}
//遍历灰度级[0,255],寻找合适的threshold
for (int i = 0; i < 256; i++)
{
w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
for (int j = 0; j < 256; j++)
{
if (j <= i) //背景部分
{
w0 += pixel_pro[j];//背景像素比例
u0_temp += j * pixel_pro[j];
}
else //前景部分
{
w1 += pixel_pro[j];//前景像素比例
u1_temp += j * pixel_pro[j];
}
}
u0 = u0_temp / w0;//背景像素点的平均灰度
u1 = u1_temp / w1;//前景像素点的平均灰度
//http://blog.163.com/yuyang_tech/blog/static/216050083201302113341762/
delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));//类间方差 g=w0*w1*(u0-u1)^2
//当类间方差delta_temp最大时,对应的i就是阈值T
if (delta_temp > delta_max)
{
delta_max = delta_temp;
threshold = i;
}
}
return threshold;
}
全部代码:
//图像的二值化
#include <opencv2/opencv.hpp>
using namespace std;
//大津法求阈值
int otsu(const IplImage *src_image)
{
double sum = 0.0;
double w0 = 0.0;
double w1 = 0.0;
double u0_temp = 0.0;
double u1_temp = 0.0;
double u0 = 0.0;
double u1 = 0.0;
double delta_temp = 0.0;
double delta_max = 0.0;
//src_image灰度级
int pixel_count[256] = { 0 };
float pixel_pro[256] = { 0 };
int threshold = 0;
uchar* data = (uchar*)src_image->imageData;//src->imageData是指向一片数据区的地址
//统计每个灰度级中像素的个数
for (int i = 0; i < src_image->height; i++)
{
for (int j = 0; j < src_image->width; j++)
{
pixel_count[(int)data[i * src_image->width + j]]++;//每个灰度级的像素数目
sum += (int)data[i * src_image->width + j];//灰度之和
}
}
cout << "平均灰度:" << sum / (src_image->height * src_image->width) << endl;
//计算每个灰度级的像素数目占整幅图像的比例
for (int i = 0; i < 256; i++)
{
pixel_pro[i] = (float)pixel_count[i] / (src_image->height * src_image->width);
}
//遍历灰度级[0,255],寻找合适的threshold
for (int i = 0; i < 256; i++)
{
w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
for (int j = 0; j < 256; j++)
{
if (j <= i) //背景部分
{
w0 += pixel_pro[j];//背景像素比例
u0_temp += j * pixel_pro[j];
}
else //前景部分
{
w1 += pixel_pro[j];//前景像素比例
u1_temp += j * pixel_pro[j];
}
}
u0 = u0_temp / w0;//背景像素点的平均灰度
u1 = u1_temp / w1;//前景像素点的平均灰度
//http://blog.163.com/yuyang_tech/blog/static/216050083201302113341762/
delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));//类间方差 g=w0*w1*(u0-u1)^2
//当类间方差delta_temp最大时,对应的i就是阈值T
if (delta_temp > delta_max)
{
delta_max = delta_temp;
threshold = i;
}
}
return threshold;
}
int main(int argc, char** argv)
{
const char *pstrWindowsBinaryTitle = "二值图";
const char *pstrWindowsSrcTitle = "原图";
IplImage *g_pGrayImage = NULL;//灰度图
IplImage *g_pBinaryImage = NULL;//二值图片
// 从文件中加载原图
IplImage *pSrcImage = cvLoadImage("qrcode.jpg", CV_LOAD_IMAGE_UNCHANGED);
if (pSrcImage ==NULL) {
cout << "Can not load images" << endl;
return -1;
}
// 转为灰度图
g_pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
cvCvtColor(pSrcImage, g_pGrayImage, CV_BGR2GRAY);
// 创建二值图
g_pBinaryImage = cvCreateImage(cvGetSize(g_pGrayImage), IPL_DEPTH_8U, 1);
//大律法求的阈值
int pos = otsu(g_pGrayImage);
// 转为二值图
cvThreshold(g_pGrayImage, g_pBinaryImage, pos, 255, CV_THRESH_BINARY);//pos 是阈值
// 显示原图
cvNamedWindow(pstrWindowsSrcTitle, CV_WINDOW_AUTOSIZE);
cvShowImage(pstrWindowsSrcTitle, pSrcImage);
// 创建二值图窗口
cvNamedWindow(pstrWindowsBinaryTitle, CV_WINDOW_AUTOSIZE);
// 显示二值图
cvShowImage(pstrWindowsBinaryTitle, g_pBinaryImage);
cout << "阈值是:"<<pos << endl;
cvWaitKey(0);
cvDestroyWindow(pstrWindowsSrcTitle);
cvDestroyWindow(pstrWindowsBinaryTitle);
cvReleaseImage(&pSrcImage);
cvReleaseImage(&g_pGrayImage);
cvReleaseImage(&g_pBinaryImage);
return 0;
}
运行结果:
原图:
二值图: