彩色图像灰度化
原理
将彩色图像转换为灰度图像的过程称为灰度化处理,灰度化处理就是使彩色的R,G,B分量值相等的过程。由于R,G,B的取值范围是0-255,所以灰度的级别只有256级,所以说灰度图像仅能表现256种颜色(灰度)。
灰度化处理的方法主要有如下3种:
1).最大值法:使R,G,B的值等于3值中最大的一个,R=G=B=max(R,G,B),最大值法会形成亮度很高的灰度图像。
2).平均值法:是R,G,B的值求出平均值,R=G=B=(R+G+B)/3,平均值法会形成较柔和的灰度图像。
3).加权平均值法:根据重要性或其他指标给R,G,B赋予不同的权值,并使R,G,B的值加权平均,R=G=B=WR+VG+UB,W,V,U分别表示权重,研究表明,人对绿色的敏感度最高,对红色次之,对蓝色的敏感度最低,因此W>V>U,实验和理论证明当W=0.30,V=0.59,U=0.11时,能得到最合理的灰度图像。
实现内容
- 将彩色BMP图像变换成为灰度图像
打开一个彩色图像,将其转化为灰度图像。 - 设置不同的平均灰度值,显示不同平均灰度的图像
利用文本框或者滚动条设置不同的灰度值,改变图像的平均灰度值,使灰度图像的明亮程度不同。 - 滚动变换所设置的平均灰度值
使灰度图像自动在不同的平均灰度值之间变换。
步骤
- 创建一个MFC项目,选择基于对话框。
- 在主对话框中增加控件,显示需要进行处理的图片,通过添加资源,导入Bitmap图片。
- 在对话框中增加图片控件,修改控件的ID以及type属性为Bitmap,在Image属性中选择导入的Bitmap图片的名称,将图片绑定到控件上。
- 在对话框中增加Static Text、Edit Control和Button控件,实现交互功能,最终形成的主界面如下所示:
- 双击名称为“确定”的按钮,编写它的事件处理程序,下面是主要代码
void CcolorTogrey3Dlg::OnBnClickedSubmit()
{
// TODO: 在此添加控件通知处理程序代码
if (((CButton *)GetDlgItem(IDC_RADIO_AVERAGE))->GetCheck()) { //平均值法
CString RAND;
GetDlgItem(IDC_EDIT_RAND)->GetWindowText(RAND); //获取输入灰度等级
int rand = _ttoi(RAND); //获取灰度级
CDC *pDC = GetDC();
BITMAPFILEHEADER bmpFlieHead;
BITMAPINFOHEADER bmpInfHead;
FILE *fpbmp = fopen("res\\PIC.bmp", "rb");//二进制读 打开,需要修改的图片
FILE *fpGray = fopen("res\\newPIC.bmp", "wb");//二进制写 打开,保存的图片
if (fpbmp == NULL)
{
MessageBox(_T("文件不存在"));
}
fread(&bmpFlieHead, 14, 1, fpbmp);
fread(&bmpInfHead, 40, 1, fpbmp);
BYTE* buffer = new BYTE[bmpFlieHead.bfSize - 54]();
int bmpWidth = bmpInfHead.biWidth;
int bmpHeight = bmpInfHead.biHeight;
fread(buffer, bmpFlieHead.bfSize - 54, 1, fpbmp);
//输出图像每行像素所占字节数,必须是4的倍数
int lineByte = (bmpWidth*bmpInfHead.biBitCount / 8 + 3) / 4 * 4;
//根据灰值化公式为输出图像赋值
for (int i = 0; i < bmpHeight; i++) //平均灰度值方法
{
for (int j = 0; j < lineByte - 2; j++)
{
BYTE color1 = BYTE((buffer[i*lineByte + j] + buffer[i*lineByte + j + 1] + buffer[i*lineByte + j + 2]) / 3);
BYTE color=get_value(rand, color1); //根据不同的灰度级确定像素的R G B 值
buffer[i*lineByte + j] = color; //针对每一个像素,将R G B设成原像素RGB值的平均值
buffer[i*lineByte + j + 1] = color;
buffer[i*lineByte + j + 2] = color;
j += 2;
}
}
fwrite(&bmpFlieHead, 14, 1, fpGray);
fwrite(&bmpInfHead, 40, 1, fpGray);
fwrite(buffer, bmpFlieHead.bfSize - 54, 1, fpGray); //将修改后的像素的R G B写入另一个图片中
fclose(fpbmp); //关闭打开的图片
fclose(fpGray);
delete[] buffer; //释放资源
CResult dlg; //打开结果对话框
dlg.DoModal();
}
else if (((CButton *)GetDlgItem(IDC_RADIO_MAX))->GetCheck()) { //最大值法
CDC *pDC = GetDC();
BITMAPFILEHEADER bmpFlieHead;
BITMAPINFOHEADER bmpInfHead;
FILE *fpbmp = fopen("res\\PIC.bmp", "rb");//二进制读 打开,需要修改的图片
FILE *fpGray = fopen("res\\newPIC.bmp", "wb");//二进制写 打开,保存的图片
if (fpbmp == NULL)
{
MessageBox(_T("文件不存在"));
}
fread(&bmpFlieHead, 14, 1, fpbmp);
fread(&bmpInfHead, 40, 1, fpbmp);
BYTE* buffer = new BYTE[bmpFlieHead.bfSize - 54]();
int bmpWidth = bmpInfHead.biWidth;
int bmpHeight = bmpInfHead.biHeight;
fread(buffer, bmpFlieHead.bfSize - 54, 1, fpbmp);
//输出图像每行像素所占字节数,必须是4的倍数
int lineByte = (bmpWidth*bmpInfHead.biBitCount / 8 + 3) / 4 * 4;
//根据灰值化公式为输出图像赋值
for (int i = 0; i < bmpHeight; i++)
{
for (int j = 0; j < lineByte - 2; j++)
{
//找出R G B中最大的值
BYTE color = max(buffer[i*lineByte + j], buffer[i*lineByte + j + 1], buffer[i*lineByte + j + 2]);
buffer[i*lineByte + j]=color; //针对每一个像素,将R G B设置成原R G B中最大的数值
buffer[i*lineByte + j + 1] = color;
buffer[i*lineByte + j + 2] = color;
j += 2;
}
}
fwrite(&bmpFlieHead, 14, 1, fpGray);
fwrite(&bmpInfHead, 40, 1, fpGray);
fwrite(buffer, bmpFlieHead.bfSize - 54, 1, fpGray);
fclose(fpGray);
delete[] buffer;
CResult dlg; //打开结果对话框,显示处理后的结果
dlg.DoModal();
}
else if (((CButton *)GetDlgItem(IDC_RADIO_WEIGHT))->GetCheck()) { //加权平均值法
CString RED;
CString GREEN;
CString BLUE;
GetDlgItem(IDC_EDIT_RED)->GetWindowText(RED); //获取输入的权重
GetDlgItem(IDC_EDIT_GREEN)->GetWindowText(GREEN);
GetDlgItem(IDC_EDIT_BLUE)->GetWindowText(BLUE);
double red = _ttof(RED); //将CString类型的变量转换成浮点类型,权重
double green = _ttof(GREEN);
double blue = _ttof(BLUE);
CDC *pDC = GetDC();
BITMAPFILEHEADER bmpFlieHead;
BITMAPINFOHEADER bmpInfHead;
FILE *fpbmp = fopen("res\\PIC.bmp", "rb");//二进制读 打开,需要修改的图片
FILE *fpGray = fopen("res\\newPIC.bmp", "wb");//二进制写 打开,保存的图片
if (fpbmp == NULL)
{
MessageBox(_T("文件不存在"));
}
fread(&bmpFlieHead, 14, 1, fpbmp);
fread(&bmpInfHead, 40, 1, fpbmp);
BYTE* buffer = new BYTE[bmpFlieHead.bfSize - 54]();
int bmpWidth = bmpInfHead.biWidth;
int bmpHeight = bmpInfHead.biHeight;
fread(buffer, bmpFlieHead.bfSize - 54, 1, fpbmp);
//输出图像每行像素所占字节数,必须是4的倍数
int lineByte = (bmpWidth*bmpInfHead.biBitCount / 8 + 3) / 4 * 4;
//根据灰值化公式为输出图像赋值
for (int i = 0; i < bmpHeight; i++)
{
for (int j = 0; j < lineByte - 2; j++)
{
BYTE r = buffer[i*lineByte + j + 2] * red;
BYTE g = buffer[i*lineByte + j + 1] * green;
BYTE b = buffer[i*lineByte + j ] * blue;
BYTE color = BYTE(r + g + b); //设置权重后的值
buffer[i*lineByte + j] = color; //针对每一个像素,将RGB设成相同的数值
buffer[i*lineByte + j + 1] = color;
buffer[i*lineByte + j + 2] = color;
j += 2;
}
}
fwrite(&bmpFlieHead, 14, 1, fpGray);
fwrite(&bmpInfHead, 40, 1, fpGray);
fwrite(buffer, bmpFlieHead.bfSize - 54, 1, fpGray);
fclose(fpGray);
delete[] buffer;
CResult dlg; //打开结果对话框
dlg.DoModal();
}
}
首先获取需要处理的而图片的句柄以及需要存放修改后的图片的另一张图片的句柄,然后读取要灰度化的图片的位图信息,保存在BYTE类型的数组里面;然后判断用户选择的灰度方式是哪一种,如果选择的是平均值法,那么在双层循环李里面,将每一个像素的R、G、B值相加之后除以3,然后调用函数get_value根据用户设置的灰度级确定最终的RGB值,然后将这一值赋给正在处理的像素;如果选择的是最大值法,则通过MAX()函数判断R、G、B中最大的值,然后根据这一值确定像素的颜色值;如果选择的是加权平均法,则首先需要获取用户输入的RGB分别的权重,并将输入的值转换成浮点数类型,获取像素的RGB值之后,将对应的分量乘以相应的权重,由于是小端存储,因此从低地址向高地址以此存放的是B、G、R值,然后将乘以权重后的分量相加,得到新的颜色值,并赋给像素。最后,将修改后的存放位图信息的数组中的信息写入保存处理结果的图片,获得的图片则是灰度化的结果。
6. 编写改变灰度级的处理函数
BYTE CcolorTogrey3Dlg::get_value(int level, BYTE v)
{
int block_num = level ;
int block_size = 256 / block_num;
for (int i = 1; i <= block_num; i++) //级数确定区间数
{
if (v > block_size * i)
continue; //找到原图像素应该对应的区间
int mid_value = block_size * i / 2; //区间中间值
int left = block_size * (i - 1);
int right = block_size * i - 1;
if (v < mid_value) //判断原图像素颜色与区间之间的关系
return left;
else
return right;
}
return v; //返回应该设置的值
}
这一段代码是参照了网上的内容,本人对这个灰度级还不是很理解。上面代码的基本思想是根据设置的灰度级将0-255划分为不同的区间,然后求出区间的中值;首先找出原图颜色值所在的去边,然后判断它的值与区间中值的大小关系,如果原图颜色值比这个区间的中值大,则将颜色值需改为区间的最大值;如果比区间中值小或相等,则修改为区间最小值。
- 在自动改变的时候,添加一个定时器,还有一个表示灰度级的静态变量就行了,这里我就不附上代码了,下面附上一个执行结果。
8.
总结
上面的代码还有很多需要改进的地方,由于时间比较紧,没时间去优化算法的执行效率和空间利用率都有待改进。