您的位置:首页 > 其它

MFC数字图像处理24位图转8位图 等四种图像色彩转换方式

2016-10-25 11:08 1081 查看
一、 实验主要思路和基本操作

本实验主要探究8位图和24位图的颜色转换。8位图具有调色板,调色板中有对应的256种不同的颜色,每种颜色所含的RGB值都不一样。24位图没有调色板,RGB三个颜色分量分别都有0-255可选择,属于真彩色图像。其中,两种不同位数的图形都有彩色图像和灰度图像两种,灰度图像中每个像素的颜色分量,R、G、值都一样。所以本实验核心分为两点:了解颜色的RGB组合和学会调色板的使用

调色板:

主要功能是节省图像字节。8位图像中,图中最多256中颜色,如果采用颜色表,表中每一行记录一种颜色的RGB值,当表示一个像素颜色时,只需要指出该颜色是在第几行,即该颜色在表中的索引值就可以。如,表第0行是255,0,0,为红色时,只需要标明0即可。通过颜色表来表示图像,256种颜色可以用8位表示,一个像素使用1个字节,比原来的每个像素使用3个字节(256+256+256,每个分量一个字节表示)节省很大空间。

24位图由于每个分量中0-255个值都有可能用到,使用调色板反而会增加3个字节的使用调色板空间。所以24位图不使用调色板。

RGB组合:彩色图像三个RGB分量不一定相同,灰色图像三个RGB值相同。0-255的颜色分量可表示各灰度像素的亮度。

基本操作:1.在资源视图中的菜单选项,建立操作菜单,并对菜单定义相应ID。

2.在类视图-属性-事件中,为菜单添加触发操作。

3.在VIEW类中编写程序。

(注:本文所有实验结果图因为无法复制粘贴,重新生成图片较为麻烦,次实验也做较久,故不贴图片显示实验结果。之后实时做的实验都会贴图,望理解。)

二、主要程序

一、8位彩色转换成8位灰度

1.原理:获取原图信息,新建一个调色板,找到该图像对应的像素值,计算该颜色对应的灰度值,通过逐行扫描更换每个像素的颜色索引,更新并保存图像。

细节:1.首先通过lpSrcColorBits=lpSrcDib+sizeof(BITMAPINFOHEADER)获取调色板的指针信息。在新建立的调色板中,利用获取的原图像调色板计算该像素点对应的灰度值。调色板的每一行有3个字节,于是以3为周期,对调色板各个像素信息进行更新。

2.进行逐行扫描时,通过指针先找到数据起始点,逐行对每一行每一列的像素值进行更新。

3.定义像素信息的指针用LPSTR类型。

2.代码实现

//8位彩色转灰度------------------------------------------------------------------------------
void CImgtestView::On88grey()
{
CImgtestDoc* pDoc=GetDocument();

long lSrcLineBytes;
long lSrcWidth;
long lSrcHeight;
int lpSrcBitCount;
LPSTR lpSrcDib;//指向源图像的指针
LPSTR lpSrcStartBits;//指向数据的指针

lpSrcDib=(LPSTR)::GlobalLock((HGLOBAL)pDoc->GetHObject());

if(!lpSrcDib) return;
if(pDoc->m_dib.GetColorNum(lpSrcDib)!=256)
{
AfxMessageBox(L"对不起,不是256色图!");
::GlobalUnlock((HGLOBAL)pDoc->GetHObject());
return;
}
lpSrcStartBits=pDoc->m_dib.GetBits(lpSrcDib);
lSrcWidth=pDoc->m_dib.GetWidth(lpSrcDib);
lSrcHeight=pDoc->m_dib.GetHeight(lpSrcDib);
lSrcLineBytes=pDoc->m_dib.GetReqByteWidth(lSrcWidth*8);
lpSrcBitCount=pDoc->m_dib.GetBitCount(lpSrcDib);
/////////////////////////////////////////////////////////

BYTE bMap[256];//灰度映射表
printf("\n对256色彩色图像变为256级灰度图像\n");

LPSTR lpSrcColorBits;//指向调色板的指针
LPSTR lpSrc;// 指向DIB第i行,第j个象素的指针
lpSrcColorBits=lpSrcDib+sizeof(BITMAPINFOHEADER);

for(int i=0;i<256;i++)//生成新的调色板,并转换灰度
{
// 计算该颜色对应的灰度值g=0.299*r+0.587*g+0.114*b
int j=0;
bMap[i]=(BYTE)(0.299 * lpSrcColorBits[j] + 0.587 * lpSrcColorBits[j+1] + 0.114 * lpSrcColorBits[j+2] + 0.5);
lpSrcColorBits[j]= lpSrcColorBits[j+1]= lpSrcColorBits[j+2]= i;
lpSrcColorBits=lpSrcColorBits+4;

}
// 更换每个象素的颜色索引(即按照灰度映射表换成灰度值)
// 逐行扫描
for(int i = 0; i < lSrcHeight; i++)
{
//逐列扫描
for(int j = 0; j < lSrcWidth; j++)
{
lpSrc = lpSrcStartBits + lSrcLineBytes * (lSrcHeight - 1 - i) + j;

*lpSrc =bMap[*lpSrc];// 变换
}
}
pDoc->SetModifiedFlag(TRUE);
pDoc->UpdateAllViews(NULL);
::GlobalUnlock((HGLOBAL)pDoc->GetHObject());

}


3.实验结果

二、24彩色位转24位灰度

1.原理:24位彩色图没有颜色表,所以仅需通过获取位图信息,找到像素点的RGB数据,对像素点进行灰度计算,更新图像,就能完成转换。

细节:1.像素的颜色分量为unsigned char类型。

2.代码实现

//24
4000
位转24位灰度------------------------------------------------------------------------------
void CImgtestView::On2424grey()
{
// TODO: 在此添加命令处理程序代码

CImgtestDoc* pDoc=GetDocument();

long lSrcLineBytes;
long lSrcWidth;
long lSrcHeight;
int lpSrcBitCount;
LPSTR lpSrcDib;//指向源图像的指针
LPBYTE lpSrcStartBits;//指向数据的指针

lpSrcDib=(LPSTR)::GlobalLock((HGLOBAL)pDoc->GetHObject());

if(!lpSrcDib) return;

lpSrcStartBits=(LPBYTE)pDoc->m_dib.GetBits(lpSrcDib);
lSrcWidth=pDoc->m_dib.GetWidth(lpSrcDib);
lSrcHeight=pDoc->m_dib.GetHeight(lpSrcDib);
lSrcLineBytes=pDoc->m_dib.GetReqByteWidth(lSrcWidth*24);
lpSrcBitCount=pDoc->m_dib.GetBitCount(lpSrcDib);
/////////////////////////////////////////////////////

if (lpSrcBitCount !=24)// 判断是否是8-bpp位图
{
AfxMessageBox(L"对不起,不是24位图!");// 警告
::GlobalUnlock((HGLOBAL) pDoc->GetHObject());// 解除锁定
return;                                  //返回
}

LPSTR lpSrcColorBits;//指向调色板的指针
LPSTR lpSrc;// 指向DIB第i行,第j个象素的指针
lpSrcColorBits=lpSrcDib+sizeof(BITMAPINFOHEADER);

unsigned char Intensity;
unsigned char *lpR,*lpB,*lpG;
for(int y=0;y<lSrcHeight;y++)
{
for(int x=0;x<lSrcWidth;x++)
{
lpR = lpSrcStartBits + lSrcLineBytes * (lSrcHeight - 1 - y) + x*3;
lpB = lpSrcStartBits + lSrcLineBytes * (lSrcHeight - 1 - y) + x*3+1;
lpG = lpSrcStartBits + lSrcLineBytes * (lSrcHeight - 1 - y) + x*3+2;
Intensity=0.299 *(* lpR) + 0.587 *(* lpB) + 0.114 * (*lpG );
if(Intensity>255)
Intensity=255;
if(Intensity<0)
Intensity=0;

*lpR= *lpB= *lpG= Intensity;
}
}
pDoc->SetModifiedFlag(TRUE);
pDoc->UpdateAllViews(NULL);
::GlobalUnlock((HGLOBAL)pDoc->GetHObject());
}


3.实验结果

三、24位彩色转8位灰度

1.原理:申请位图数据所需要的空间,将新图像位数改为8位,建立新的调色板,申请颜色表需要的空间,给颜色表赋值,逐行扫描,对各像素点进行灰度变换,更新图像。

细节:1.申请新图像时,对图像位数进行更改。

2.颜色表的定义 RGBQUAD* pColorTable

2.代码实现

//24位转8位灰度------------------------------------------------------------------------------
void CImgtestView::On248grey()
{
// TODO: 在此添加命令处理程序代码

CImgtestDoc* pDoc=GetDocument();

long lSrcLineBytes;     //图象每行的字节数
long    lSrcWidth;      //图象的宽度和高度
long    lSrcHeight;
int     lpSrcBitCount;       //图像的位深
LPSTR   lpSrcDib;       //指向源图象的指针
LPSTR   lpSrcStartBits; //指向源像素的指针
lpSrcDib= (LPSTR) ::GlobalLock((HGLOBAL) pDoc->GetHObject());// 锁定DIB
if (!lpSrcDib) return;
if (pDoc->m_dib.GetBitCount(lpSrcDib) != 24)// 判断是否是24-bpp位图
{
AfxMessageBox(L"对不起,不是24位图!");// 警告
::GlobalUnlock((HGLOBAL) pDoc->GetHObject());// 解除锁定
return;                                  //返回
}
lpSrcStartBits=pDoc->m_dib.GetBits(lpSrcDib);           // 找到DIB图象像素起始位置
lSrcWidth= pDoc->m_dib.GetWidth(lpSrcDib);                  // 获取图象的宽度
lSrcHeight= pDoc->m_dib.GetHeight(lpSrcDib);                    // 获取图象的高度
lpSrcBitCount=pDoc->m_dib.GetBitCount(lpSrcDib);                    //获取图像位深
lSrcLineBytes=pDoc->m_dib.GetReqByteWidth(lSrcWidth * lpSrcBitCount);       // 计算图象每行的字节数
/////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char*  lpSrc;

// 循环变量
LONG    i;
LONG    j;
// 图像每行的字节数
LONG    lLineBytes;
// 计算图像每行的字节数
lLineBytes = WIDTHBYTES(lSrcWidth * 24);
BITMAPINFOHEADER *pHead;
RGBQUAD* pColorTable;
int lineByteNew;
int biBitCount=8;
lineByteNew=(lSrcWidth * biBitCount/8+3)/4*4;
//申请位图数据所需要的空间,读位图数据进内存
unsigned char *pBmpBufNew;
pBmpBufNew=new unsigned char[lineByteNew * lSrcHeight + sizeof(BITMAPINFOHEADER) + 256*4];//申请新图像的空间
memcpy(pBmpBufNew,(unsigned char *)lpSrcDib,sizeof(BITMAPINFOHEADER));//信息头拷贝
pHead=(BITMAPINFOHEADER *)pBmpBufNew;
pHead->biBitCount=8;//改变位数,
pHead->biHeight=lSrcHeight;
pHead->biWidth=lSrcWidth;
pHead->biClrUsed=256;
pHead->biClrImportant=0;
pHead->biCompression=0;
pHead->biPlanes=1;
pHead->biSize=40;
pHead->biSizeImage=lineByteNew*lSrcHeight;
pHead->biXPelsPerMeter=0;
pHead->biYPelsPerMeter=0;
pColorTable=(RGBQUAD*)(pBmpBufNew +sizeof(BITMAPINFOHEADER));
//灰度图像有颜色表,且颜色表表项为
if(biBitCount==8){
//申请颜色表所需要的空间,给颜色表赋值
for(int i=0;i<256;i++)
{
pColorTable[i].rgbBlue=i;
pColorTable[i].rgbGreen=i;
pColorTable[i].rgbRed=i;
pColorTable[i].rgbReserved=0;
}
}

int Red,Green,Blue,Gray,offset;
offset=sizeof(BITMAPINFOHEADER)+256*4;
//逐行扫描
for(i = 0; i < lSrcHeight; i++)
{
//逐列扫描
for(j = 0; j < lSrcWidth; j++)
{
// 指向DIB第i行,第j个象素的指针
lpSrc = (unsigned char*)lpSrcStartBits+ lLineBytes * (lSrcHeight - 1 - i) + j*3;
Blue = *lpSrc;
Green=*(++lpSrc);
Red=*(++lpSrc);
Gray= (BYTE)(0.299 * Red + 0.587 * Green + 0.114 * Blue + 0.5);
// 变换
lpSrc= (unsigned char*)(pBmpBufNew+offset) + lineByteNew * (lSrcHeight - 1 - i) + j;
*lpSrc =Gray;
}
}
//拷贝
memcpy(lpSrcDib,pBmpBufNew,lineByteNew * lSrcHeight+ sizeof(BITMAPINFOHEADER) + 256*4);

delete []pBmpBufNew;

//设置文档修改标志
pDoc->SetModifiedFlag(true);
//更新视图
pDoc->UpdateAllViews(NULL);
//解除锁定
::GlobalUnlock((HGLOBAL)pDoc->GetHObject ());

}


3.实验结果

四、24位彩色转8位彩色

1.原理:1.建立八叉树,合并子树。将拥有256*256*256色的真彩色图像,转换为256色图像。八叉树节点的特性就是每个节点最多有8个字节点,编号为0~7 。以RGB值建立八叉树,首先建立根节点,然后分别以RGB的每一位分别组成一个0~7的值,依次插入树中。以RGB(123,54,78)为例。

以此类推,将所有的RGB值逐层插入到八叉树中,在每个节点上,记录所有经过的节点的RGB值的总和,以及RGB颜色个数。插入的过程中,如果节点不存在,则需要创建新的节点,然后增加节点计数以及RGB各分量的总和.当在插入时,发现节点已经存在,且是叶子节点,则停止该颜色后续层数节点的插入。插入完一个颜色之后,如果叶子节点数超过了我们要得到的颜色数(256色需要得到256种颜色),这时候就需要合并一些呀字节点了,使的叶子节点的个数不超过我们要得到的颜色数。

由于越底层的节点,数据的敏感度越低,所以,我们将从最底层的节点开始合并。按节点计数值小的优先合并策略,将其字节点的所有RGB分量以及节点计数全部记录到该节点中,并删除其所有子节点。依此进行,直到合并后的叶子数符合要求为止。

2.提取调色板 按照上述的步骤插入完所有的颜色之后,便建立起一颗叶子节点不超过256的八叉树。此时,取出叶子节点中的RGB分量的平均值(分量总和 / 节点计数),即是得到的调色板颜色值。

3.匹配调色板索引 所谓匹配调色板索引,就是根据原始的RGB值,在调色板中查找出最接近的颜色的索引。对每个RGB颜色,分别对调色板数据求各分量的差值的平方和,求的的最小值对应的调色板颜色的索引,即是该RGB颜色匹配到的调色板索引。

细节:1.定义直接插入排序(sort1)和快速排序(sort2)两种排序方式,通过两种排序方式,建立八叉树。计算各个像素值的使用频率,将使用最高的前256种,定义为调色板。其中usedTimes数组中是各颜色使用频率。并用原来的useTimes数组来保存索引值

2.用PFC函数计算,当像素值不在颜色表256位时,寻找这256种中最接近的颜色。

代码实现

//数学函数
int PFC(int color1, int color2)
{
int x,y,z;
x = (color1 & 0xf) - (color2 &
d5e6
0xf);
y = ((color1>>4) & 0xf) - ((color2>>4) & 0xf);
z = ((color1>>8) & 0xf) - ((color2>>8) & 0xf);
return (x*x + y*y + z*z);
}

//直接插入排序
int Sort1(int *src, int *attach, int n)
{
int cur, cur1;
int i,j,k=0;
for (i = 1; i < n; i++)
{
cur     = src[i];
cur1 = attach[i];
for (j = i - 1; j >= 0; j--)
{
if (cur > src[j])
{
src[j+1]    = src[j];
attach[j+1] = attach[j];
}
else
break;
}
src[j+1]  = cur;
attach[j+1] = cur1;
}
return 0;
}

//快速排序
int Sort2(int *src, int *attach, int n)
{
if (n <= 12)
return Sort1(src, attach, n);
int low = 1, high = n - 1;
int tmp;
while (low <= high)
{
while (src[low] >= src[0])
{
if (++low > n - 1)
break;
}
while (src[high] < src[0])
{
if (--high < 1)
break;
}
if (low > high)
break;
{
tmp                = src[low];
src[low]        = src[high];
src[high]        = tmp;
tmp                = attach[low];
attach[low]        = attach[high];
attach[high]    = tmp;
}
low++;
high--;
}

{
tmp                = src[low - 1];
src[low - 1]    = src[0];
src[0]            = tmp;
tmp                = attach[low - 1];
attach[low - 1]    = attach[0];
attach[0]        = tmp;
}
if (low > 1)
Sort2(src, attach, low - 1);
if (low < n)
Sort2(&src[low], &attach[low], n - low);
return 0;
}

int Transfer(WORD *color24bit, int len, BYTE *Index, RGBQUAD *mainColor)
{
int usedTimes[4096] = {0};
int miniColor[4096];
int i;
for ( i = 0; i < 4096; i++)
miniColor[i] = i;
i = 0;
for (i = 0; i < len; i++)
{
assert(color24bit[i] < 4096);
usedTimes[color24bit[i]]++;
}

int numberOfColors = 0;
for (i = 0; i < 4096; i++)
{
if (usedTimes[i] > 0)
numberOfColors++;
}

//对usedTimes进行排序,排序过程中minColor数组(保存了颜色值)也作与useTimes
//数组相似的交换
Sort2(usedTimes, miniColor, 4096);

//usedTimes数组中是各颜色使用频率,从高到低排列,显然第numberOfColor个之后的都为0
//miniColor数组中是相应的颜色数据
//将前256个颜色数据保存到256色位图的调色盘中
for (i = 0; i < 256; i++)
{
mainColor[i].rgbBlue    = (BYTE)((miniColor[i]>>8)<<4);
mainColor[i].rgbGreen    = (BYTE)(((miniColor[i]>>4) & 0xf)<<4);
mainColor[i].rgbRed        = (BYTE)((miniColor[i] & 0xf)<<4);
mainColor[i].rgbReserved = 0;
}

int *colorIndex = usedTimes;//用原来的useTimes数组来保存索引值
memset(colorIndex, 0, sizeof(int) * 4096);

if (numberOfColors <= 256)
{
for (i = 0; i < numberOfColors; i++)
colorIndex[miniColor[i]] = i;
}
else//为第256之后的颜色在前256种颜色中找一个最接近的
{
for (i = 0; i < 256; i++)
colorIndex[miniColor[i]] = i;

int index, tmp, tmp1;
for (i = 256; i < numberOfColors; i++)
{
tmp      = PFC(miniColor[0], miniColor[i]);
index = 0;
for (int j = 1; j < 256; j++)
{
tmp1 = PFC(miniColor[j], miniColor[i]);
if (tmp > tmp1)
{
tmp = tmp1;
index = j;
}
}
colorIndex[miniColor[i]] = index;
}
}
//记录各点颜色数据的索引值,即256色位图的颜色数据
for (i = 0; i < len; i++)
{
assert(colorIndex[color24bit[i]] < 256);
Index[i] = colorIndex[color24bit[i]];
}

return 1;
}

//24位转8位彩色------------------------------------------------------------------------------
void CImgtestView::On248color()
{
// TODO: 在此添加命令处理程序代码

CImgtestDoc* pDoc=GetDocument();// 获取文档
////////////////////////////////////////////////////////////////////////////////////////////////
long lSrcLineBytes;     //图象每行的字节数
long    lSrcWidth;      //图象的宽度和高度
long    lSrcHeight;
int     lpSrcBitCount;       //图像的位深
LPSTR   lpSrcDib;       //指向源图象的指针
LPSTR   lpSrcStartBits; //指向源像素的指针
lpSrcDib= (LPSTR) ::GlobalLock((HGLOBAL) pDoc->GetHObject());// 锁定DIB
if (!lpSrcDib) return;
if (pDoc->m_dib.GetBitCount(lpSrcDib) != 24)// 判断是否是24-bpp位图
{
AfxMessageBox(L"对不起,不是24位图!");// 警告
::GlobalUnlock((HGLOBAL) pDoc->GetHObject());// 解除锁定
return;                                  //返回
}
lpSrcStartBits=pDoc->m_dib.GetBits(lpSrcDib);           // 找到DIB图象像素起始位置
lSrcWidth= pDoc->m_dib.GetWidth(lpSrcDib);                  // 获取图象的宽度
lSrcHeight= pDoc->m_dib.GetHeight(lpSrcDib);                    // 获取图象的高度
lpSrcBitCount=pDoc->m_dib.GetBitCount(lpSrcDib);                    //获取图像位深
lSrcLineBytes=pDoc->m_dib.GetReqByteWidth(lSrcWidth * lpSrcBitCount);       // 计算图象每行的字节数
/////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char*  lpSrc;
// 循环变量
LONG    i;
LONG    j;
// 图像每行的字节数
LONG    lLineBytes;
// 计算图像每行的字节数
lLineBytes = WIDTHBYTES(lSrcWidth * 24);
BITMAPINFOHEADER *pHead;
RGBQUAD* pColorTable=NULL;
int lineByteNew;
int biBitCount=8;
lineByteNew=(lSrcWidth * biBitCount/8+3)/4*4;
//灰度图像有颜色表,且颜色表表项为
if(biBitCount==8){
//申请颜色表所需要的空间,给颜色表赋值
pColorTable=new RGBQUAD[256];
memset(pColorTable, 0, sizeof(RGBQUAD)*256);
}
BYTE* Index = new BYTE[lineByteNew*lSrcHeight]; //图像数据区的数据(保存在Index中)
WORD* shortColor = new WORD[lineByteNew*lSrcHeight]; //颜色的高4位
int iRed, iGreen, iBlue;
for (int i = 0; i < lSrcHeight; i++)
{//取RGB颜色的高4位
for(int j=0;j<lSrcWidth;j++)
{
lpSrc = (unsigned char*)lpSrcStartBits+ lLineBytes * (lSrcHeight - 1 - i) + j*3;
iBlue = (*lpSrc)>>4;
iGreen=(*(++lpSrc))>>4;
iRed=(*(++lpSrc))>>4;
shortColor[lineByteNew * (lSrcHeight - 1 - i) + j] =(iBlue<<8) + (iGreen<<4) + iRed ;
}
}

//调用转换函数  24color To->8color
Transfer(shortColor, lineByteNew*lSrcHeight, Index, pColorTable);
/* Transfer(shortColor, nData/3, Index, mainColor);  */

//申请位图数据所需要的空间,读位图数据进内存
unsigned char *pBmpBufNew;
pBmpBufNew=new unsigned char[sizeof(BITMAPINFOHEADER) + 256*4];//申请新图像的空间
memcpy(pBmpBufNew,(unsigned char *)lpSrcDib,sizeof(BITMAPINFOHEADER));//信息头拷贝
pHead=(BITMAPINFOHEADER *)pBmpBufNew;
pHead->biBitCount=8;//改变位数,
pHead->biHeight=lSrcHeight;
pHead->biWidth=lSrcWidth;
pHead->biClrUsed=256;
pHead->biClrImportant=0;
pHead->biCompression=0;
pHead->biPlanes=1;
pHead->biSize=40;
pHead->biSizeImage=lineByteNew*lSrcHeight;
pHead->biXPelsPerMeter=0;
pHead->biYPelsPerMeter=0;

//拷贝
memcpy(lpSrcDib,pBmpBufNew, sizeof(BITMAPINFOHEADER));
memcpy(lpSrcDib+sizeof(BITMAPINFOHEADER),pColorTable, sizeof(RGBQUAD)*256);
memcpy(lpSrcDib+sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256,Index,lineByteNew*lSrcHeight);

delete []Index;
delete []shortColor;

//设置文档修改标志
pDoc->SetModifiedFlag(true);
//更新视图
pDoc->UpdateAllViews(NULL);
//解除锁定
::GlobalUnlock((HGLOBAL)pDoc->GetHObject ());

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐