前提知识
用十六进制查看图像文件需要注意:
beyond compare/notpad++查看图像的十六进制文件,数值数据是小端模式存放的二进制和数据在内存中的表现一致,只是大于1字节的数据在内存中赋值(通过结构体赋值也是一样的)给相应的整型时,不用大小端转换,赋值后会直接得到整型的结果。
1. 什么是灰度图?
灰度图的RGB值相等
灰度图调色板的值就是ARGB 205,0,0,0到205,255,255,255的像素值,灰度图就是黑白两色在深度上面的变化256种黑白灰度颜色,不同于单纯黑白两色。灰度图的位图数据部分存放的是灰度图调色板的索引。
在非图像学术领域,灰度图的照片,灰度图的电影,也叫黑白照片和黑白电视,黑白电影。
一般使用8位的灰度图,但是医学,航拍中需要更高的精度,而采用16位灰度图像。
2.灰度图的作用?
灰度图是只含有黑白颜色,和0~255亮度等级的图片。灰度图具有
存储小,其亮度值就是256色调色板索引号,
从整幅图像的整体和局部的色彩以及亮度等级分布特征来看,灰度图描述与彩色图的描述是一致的特点。因此很多真彩色图片的分析,第一步就是转换为灰度图,然后再进行分析。
真彩色,因为是24位,2(^8) * 2(^8)
* 2(^8) = 16777216种颜色,需要调色板16777216 * 4byte字节的空间也就是64MB的调色板空间,所以真彩色是不用调色板的。
例如视频目标跟踪和识别时,第一步就是要转换为灰度图。现有的成熟分析算法多是基于灰度图像的,灰度图像综合了真彩色位图的RGB各通道的信息。
3.真彩色图片转换为灰度图的常用方法?
第一种方法是根据YUV的颜色空间中,Y的分量的物理意义是点的亮度,由该值反映亮度等级,根据RGB和YUV颜色空间的变化关系可建立亮度Y与R、G、B三个颜色分量的对应:Y=0.3R+0.59G+0.11B,以这个亮度值表达图像的灰度值。
第二种方法使求出每个像素点的R、G、B三个分量的平均值,然后将这个平均值赋予给这个像素的三个分量。
具体这两种方法,根据什么应用更应该选择哪一种方法,我还不了解,知道的麻烦告诉我下,非常感激。
win32下代码实现例子:
#include <stdio.h>
#include <string>
#include <math.h>
#include <windows.h>
using namespace std;
// 灰度图公式函数指针,缺点是函数调用降低了执行效率,优点是可以灵活的选择灰化公式
typedef int (*GrayFunction)(int nRed, int nGreen, int nBlue);
// Gray = R*0.3+G*0.59+B*0.11
int RegularGray(int nRed, int nGreen, int nBlue)
{
// 转换为整型和位运算除法,可以更有效的提高效率
float fGray = /*float(*/0.3f * nRed + 0.59f * nGreen + 0.11f * nBlue;
return int(fGray);
}
// Gray=(R+G+B)/3;
int AverageGray(int nRed, int nGreen, int nBlue)
{
float fGray = float(nRed + nGreen + nBlue) / 3;
return int(fGray);
}
//将位图转换为256色灰度图
void ToGray(const string& srcFile,const string& desFile, GrayFunction func)
{
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bmiHeader;
FILE *pFile;
if ((pFile = fopen(srcFile.c_str(),"rb")) == NULL)
{
printf("open bmp file error.");
exit(-1);
}
//读取文件和Bitmap头信息
fseek(pFile,0,SEEK_SET);
fread(&bmfHeader,sizeof(BITMAPFILEHEADER),1,pFile);
fread(&bmiHeader,sizeof(BITMAPINFOHEADER),1,pFile);
//先不支持16位位图
int bitCount = bmiHeader.biBitCount;
if (bitCount == 16)
{
exit(-1);
}
double byteCount = (double)bitCount / 8;
int nClr = 0;
if (bitCount < 16)
{
nClr = bmiHeader.biClrUsed ? bmiHeader.biClrUsed : 1 << bitCount;
if (nClr > 256)
nClr = 0;
}
//读取调色板
RGBQUAD *quad = NULL;
if (nClr > 0)
{
quad = new RGBQUAD[nClr];
fread(quad,sizeof(RGBQUAD) * nClr,1,pFile);
}
int srcW = bmiHeader.biWidth;
int srcH = bmiHeader.biHeight;
//原图像每一行去除偏移量的字节数
int lineSize = bitCount * srcW >> 3;
//偏移量,windows系统要求每个扫描行按四字节对齐
// 数n加上一个数r-1,又与上非r-1,其实是求得数n加上足够的偏移后[n, n+r-1]内的关于r的唯一倍数k。
// 数k是数n不经过填充或者经过最小填充后的是r的倍数。
// alignBytes是不用填充或者填充后的,相对于原来的数,填充的字节数。
int alignBytes = (((bmiHeader.biWidth * bitCount + 31) & ~31) >> 3)
- ((bmiHeader.biWidth * bitCount) >> 3);
//原图像缓存
int srcBufSize = lineSize * srcH;
BYTE* srcBuf = new BYTE[srcBufSize];
int i,j;
//读取文件中数据
for (i = 0; i < srcH; i++)
{
// 按照BYTE读取进来,也就是BGRA形式读取进来到内存里面了。
fread(&srcBuf[lineSize * i],lineSize,1,pFile);
fseek(pFile,alignBytes,SEEK_CUR);
}
//256色位图调色板
RGBQUAD testData,*pTestData = new RGBQUAD;// RGBQUAD结构体默认构造函数是给每个通道赋值204,new时候是给每个分量205.
RGBQUAD *quadDes = NULL;
quadDes = new RGBQUAD[256];
for (i = 0; i < 256; i++)
{
//灰度图的RGB值相等
// 灰度图调色板的值就是ARGB 205,0,0,0到205,255,255,255的像素值,灰度图就是黑白两色在深度上面的变化256种,不同于单纯黑白两色。
// 在非图像学术领域,灰度图的照片,灰度图的电影,也叫黑白照片和黑白电视,黑白电影。
quadDes[i].rgbBlue = quadDes[i].rgbGreen = quadDes[i].rgbRed = i;
testData = quadDes[i];
//printf("testData: %d: %d: %d: %d\n",i,i,i,quadDes[i].rgbReserved);
}
delete pTestData;
//灰度图每个像素采用8位表示,每行对齐的字节数(包括对齐填充字节),window需要按照4字节对齐。
int nLineByteCountIncludeAlign = (((srcW * 8 + 31) & ~31) >> 3);
// 高度也是一个像素一个字节,所以desBufSize是总的图片位图数据字节数
int desBufSize = nLineByteCountIncludeAlign * srcH;
BYTE *desBuf = new BYTE[desBufSize];
//每个扫描行占用字节数
int desLineSize = nLineByteCountIncludeAlign/*((srcW * 8 + 31) >> 5) * 4*/;
for (i = 0; i < srcH; i++)
{
for (j = 0; j < srcW; j++)
{
//从调色板中读取RGB值
if (nClr > 0)
{
// 获得位图数据表示的调色板索引值
unsigned int pos = srcBuf[i * lineSize + int(j * byteCount)];
// 根据调色板索引到调色板取位图像素
desBuf[i * desLineSize + j] = func( quad[pos].rgbRed, quad[pos].rgbGreen, quad[pos].rgbBlue );
}
else
{
// 直接从真彩色的位图数据中取得像素转换为灰度图索引
//srcBuf是BGRA方式将位图数据读取到内存里面去了
desBuf[i * desLineSize + j] = func( srcBuf[i * lineSize + int(j * byteCount) + 2] , \
srcBuf[i * lineSize + int(j * byteCount) + 1], \
srcBuf[i * lineSize + int(j * byteCount)] );
//printf("PixelIndexData: %d\n",desBuf[i * desLineSize + j]);
}
}
}
//创建目标文件
HFILE hfile = _lcreat(desFile.c_str(),0);
//文件头信息
BITMAPFILEHEADER nbmfHeader;
nbmfHeader.bfType = 0x4D42;
nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
+ 256 * sizeof(RGBQUAD) + srcW * srcH;
nbmfHeader.bfReserved1 = 0;
nbmfHeader.bfReserved2 = 0;
nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);
//Bitmap头信息
BITMAPINFOHEADER bmi;
bmi.biSize=sizeof(BITMAPINFOHEADER);
bmi.biWidth=srcW;
bmi.biHeight=srcH;
bmi.biPlanes=1;
bmi.biBitCount=8;
bmi.biCompression=BI_RGB;
bmi.biSizeImage=0;
bmi.biXPelsPerMeter=0;
bmi.biYPelsPerMeter=0;
bmi.biClrUsed= 256;
bmi.biClrImportant=0;
//写入文件头信息
_lwrite(hfile,(LPCSTR)&nbmfHeader,sizeof(BITMAPFILEHEADER));
//写入Bitmap头信息
_lwrite(hfile,(LPCSTR)&bmi,sizeof(BITMAPINFOHEADER));
if (quadDes)
{
_lwrite(hfile,(LPCSTR)quadDes,sizeof(RGBQUAD) * 256);
}
//写入图像数据
_lwrite(hfile,(LPCSTR)desBuf,desBufSize);
_lclose(hfile);
if (quad)
{
delete[] quad;
quad = NULL;
}
if (quadDes)
{
delete[] quadDes;
quadDes = NULL;
}
}
int main(int argc, char* argv[])
{
string srcFile("f://data//apple.bmp");
string desFile("f://data//applegray2.bmp");
ToGray(srcFile,desFile, AverageGray/*RegularGray*/);
system("pause");
return 0;
}