MFC空间几何变换之图像平移、镜像、旋转、缩放
本文主要講述基于VC++6.0 MFC圖像處理的應(yīng)用知識,主要結(jié)合自己大三所學(xué)課程《數(shù)字圖像處理》及課件進(jìn)行講解,主要通過MFC單文檔視圖實現(xiàn)顯示BMP圖片空間幾何變換,包括圖像平移、圖形旋轉(zhuǎn)、圖像反轉(zhuǎn)倒置鏡像和圖像縮放的知識。同時文章比較詳細(xì)基礎(chǔ),沒有采用GDI+獲取矩陣,而是通過讀取BMP圖片信息頭和矩陣像素實現(xiàn)變換,希望該篇文章對你有所幫助,尤其是初學(xué)者和學(xué)習(xí)圖像處理的學(xué)生。
? ? ? ?【數(shù)字圖像處理】一.MFC詳解顯示BMP格式圖片
? ? ? ?【數(shù)字圖像處理】二.MFC單文檔分割窗口顯示圖片
? ? ? ?【數(shù)字圖像處理】三.MFC實現(xiàn)圖像灰度、采樣和量化功能詳解
? ? ? ?【數(shù)字圖像處理】四.MFC對話框繪制灰度直方圖
? ? ? ?【數(shù)字圖像處理】五.MFC圖像點運算之灰度線性變化、灰度非線性變化、閾值化和均衡化處理詳解
? ? ? ??免費資源下載地址:
? ? ? ??http://download.csdn.net/detail/eastmount/8772951
?
一. 圖像平移
?
? ? ? ?前一篇文章講述了圖像點運算(基于像素的圖像變換),這篇文章講述的是圖像幾何變換:在不改變圖像內(nèi)容的情況下對圖像像素進(jìn)行空間幾何變換的處理方式。
? ? ? ??點運算對單幅圖像做處理,不改變像素的空間位置;代數(shù)運算對多幅圖像做處理,也不改變像素的空間位置;幾何運算對單幅圖像做處理,改變像素的空間位置,幾何運算包括兩個獨立的算法:空間變換算法和灰度級插值算法。
? ? ? ? 空間變換操作包括簡單空間變換、多項式卷繞和幾何校正、控制柵格插值和圖像卷繞,這里主要講述簡單的空間變換,如圖像平移、鏡像、縮放和旋轉(zhuǎn)。主要是通過線性代數(shù)中的齊次坐標(biāo)變換。
? ? ? ? 圖像平移坐標(biāo)變換如下:
? ? ? ? 運行效果如下圖所示,其中BMP圖片(0,0)像素點為左下角。
? ? ? ? 其代碼核心算法:
? ? ? ? 1.在對話框中輸入平移坐標(biāo)(x,y) m_xPY=x,m_yPY=y
? ? ? ? 2.定義Place=dlg.m_yPY*m_nWidth*3 表示當(dāng)前m_yPY行需要填充為黑色
? ? ? ? 3.新建一個像素矩陣?ImageSize=new unsigned char[m_nImage]
? ? ? ? 4.循環(huán)整個像素矩陣處理?
? ? ? ? ? ? ?for(int i=0 ; i<m_nImage ; i++ ){
? ? ? ? ? ? ? ? ? ?if(i<Place) {ImageSize[i]=black;?continue;}?//黑色填充底部 從小往上繪圖
? ? ? ? ? ? ? ? ? ?else if(i>=Place && countWidth<dlg.m_xPY*3) {//黑色填充左部分
? ? ? ? ? ? ? ? ? ? ? ? ?ImageSize[i]=black;?countWidth++; ?continue;
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?else if(i>=Place && countWidth>=dlg.m_xPY*3) {//圖像像素平移區(qū)域
? ? ? ? ? ? ? ? ? ? ? ??ImageSize[i]=m_pImage[m_pImagePlace];//原(0,0)像素賦值過去
? ? ? ? ? ? ? ? ? ? ? ??m_pImagePlace++;?countWidth++;
? ? ? ? ? ? ? ? ? ? ? ??if(countWidth==m_nWidth*3) {?//一行填滿?m_pImagePlace走到(0,1)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??number++;?m_pImagePlace=number*m_nWidth*3;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ?}
? ? ? ? ?5.寫文件繪圖fwrite(ImageSize,m_nImage,1,fpw)
? ? ? ? 第一步:在ResourceView資源視圖中,添加Menu子菜單如下:(注意ID號)
? ? ? ? 第二步:設(shè)置平移對話框。將試圖切換到ResourceView界面--選中Dialog,右鍵鼠標(biāo)新建一個Dialog,并新建一個名為IDD_DIALOG_PY。編輯框(X)IDC_EDIT_PYX 和 (Y)IDC_EDIT_PYY,確定為默認(rèn)按鈕。設(shè)置成下圖對話框:
? ? ? ? 第三步:在對話框資源模板空白區(qū)域雙擊鼠標(biāo)—Create a new class創(chuàng)建一個新類--命名為CImagePYDlg。會自動生成它的.h和.cpp文件。打開類向?qū)?Ctrl W),選擇類名:CImagePYDlg添加成員變量如下圖所示,同時在Message Maps中生成ID_JHBH_PY實現(xiàn)函數(shù)。
?
? ? ? ? 第四步:在CImageProcessingView.cpp中添加頭文件#include "ImagePYDlg.h",并實現(xiàn)平移。
/********************************************************/
/* 圖像空間幾何變換:圖像平移 ID_JHBH_PY(幾何變換-平移)
/* 使用平移對話框:CImagePYDlg dlg
/* 算法:f(x,y)=f(x+x0,y+y0)圖像所有點平移,空的補黑'0'
/* 注意該圖像平移方法只是從左上角(0,0)處開始平移
/* 其他方向原理相同 自己去實現(xiàn)
/********************************************************/
void CImageProcessingView::OnJhbhPy()
{
if(numPicture==0) {
AfxMessageBox("載入圖片后才能空間平移!",MB_OK,0);
return;
}
//定義采樣對話框也是用來空間變換平移的坐標(biāo)
CImagePYDlg dlg;
if( dlg.DoModal()==IDOK ) //顯示對話框
{
//采樣坐標(biāo)最初為圖片的自身像素
if( dlg.m_xPY>m_nWidth || dlg.m_yPY>m_nHeight ) {
AfxMessageBox("圖片平移不能為超過原圖長寬!",MB_OK,0);
return;
}
AfxMessageBox("圖片空間變換-平移!",MB_OK,0);
//打開臨時的圖片 讀寫文件
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/************************************************************/
/* 圖片空間變換-平移
/* 坐標(biāo)(dlg.m_xPY,dlg.m_yPY)表示圖像平移的坐標(biāo)
/* 先用Plave計算出平移后的起始坐標(biāo),其他的坐標(biāo)賦值為'0'黑色
/* 然后依次平移坐標(biāo),空的賦為黑色,否則填充
/************************************************************/
/******************************************************************/
/* 嚴(yán)重錯誤1:數(shù)組變量賦值相等
/* 在View.h中定義變量 BYTE *m_pImage 讀入圖片數(shù)據(jù)后的指針
/* 建立臨時變量數(shù)組,讓它平移變換 unsigned char *ImageSize
/* ImageSize=m_pImage(錯誤)
/* 會導(dǎo)致ImageSize賦值變換時m_pImage也產(chǎn)生了變換,所以輸出全為黑色
/* 因為它倆指向了相同的數(shù)組地址
/* 解決方法:使用下面C++的new方法動態(tài)分配或for循環(huán)i=m_nImage賦值
/******************************************************************/
/*臨時變量存儲的像素與m_pImage相同,便于處理圖像*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage]; //new和delete有效的進(jìn)行動態(tài)內(nèi)存的分配和釋放
int Place; //建立臨時坐標(biāo) 記錄起始坐標(biāo)(0,0)平移過來的位置
int m_pImagePlace; //原始圖像平移為(0,0) 圖像把它平移到Place位置
unsigned char black; //填充黑色='0'
/************************************************************/
/* for(int i=0 ; i<m_nHeight ; i++ )
/* for(int j=0 ; j<m_nWidth ; j++ )
/* 不能使用的上面的因為可能圖像的最后一行沒有完整的一行像素
/* 這樣會出現(xiàn)exe報錯,使用m_nImage讀寫所有像素比較正確
/************************************************************/
Place=dlg.m_yPY*m_nWidth*3; //前m_yPY行都要填充為黑色
black=0; //顏色為黑色
m_pImagePlace=0; //圖像處事位置為(0,0),把該點像素平移過去
int countWidth=0; //記錄每行的像素個數(shù),滿行時變回0
int number=0; //數(shù)字記錄使用的像素行數(shù),平移時使用
for(int i=0 ; i<m_nImage ; i++ )
{
/*如果每行的像素填滿時清為0*/
if(countWidth==m_nWidth*3) {
countWidth=0;
}
/*第一部分:到平移后像素位置前面的所有像素點賦值為黑色*/
if(i<Place) {
ImageSize[i]=black; //賦值為黑色
continue;
}
/*第二部分:平移區(qū)域的左邊部分賦值為黑色*/
else if(i>=Place && countWidth<dlg.m_xPY*3) { //RGB乘3
ImageSize[i]=black; //賦值為黑色
countWidth++;
continue;
}
/****************************/
/* 各部分如圖所示:
/* 000000000000000000000000
/* 000000000000000000000000
/* 0000000.................
/* 0000000.................
/* 0000000.................
/* 0000000.................
/* 點表示像素部分,0為黑色
/****************************/
/* 重點錯誤提示:由于bmp圖像顯示是從左下角開始存儲(0,0)點所以輸出圖像為 */
/* bmp圖像是從左下角到右上角排列的 */
/****************************/
/* 各部分如圖所示:
/* 0000000.................
/* 0000000.................
/* 0000000.................
/* 0000000.................
/* 000000000000000000000000
/* 000000000000000000000000
/* 點表示像素部分,0為黑色
/****************************/
/*第三部分:圖像像素平移區(qū)域*/
else if(i>=Place && countWidth>=dlg.m_xPY*3)
{
ImageSize[i]=m_pImage[m_pImagePlace];
m_pImagePlace++;
countWidth++;
if(countWidth==m_nWidth*3)
{
number++;
m_pImagePlace=number*m_nWidth*3;
}
}
}
fwrite(ImageSize,m_nImage,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200; //200表示幾何變換
Invalidate();
}
}
? ? ? ? 同時在ShowBitmap中添加level標(biāo)記重新繪制圖片,代碼如下:
else //圖像幾何變換
if(level=200)
{
m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
? ? ? ?運行時需要注意一點:BMP圖像在處理過程中可能會出現(xiàn)一些斜線,而平移(40,60)位移量時可能出現(xiàn)如下。他是因為BMP格式有個非常重要的規(guī)定,要求每一掃描的字節(jié)數(shù)據(jù)必須能被4整除,也就是Dword對齊(長度4字節(jié)),如果圖像的一行字節(jié)數(shù)不能被4整除,就需要在每行末尾不起0達(dá)到標(biāo)準(zhǔn)。
? ? ? ? 例如一行像素為97字節(jié),我們就需要補3個字節(jié)嗎,數(shù)值可以是0,但是我們在BMP格式的信息頭里說明了其寬度,所以補齊后對我們沒有影響,所以后面補若干個字節(jié)的0即可直到被4整除。
?
? ? ? ? 通過后面的圖像縮放后,我從學(xué)做了一遍這個補齊的縮放。代碼如下,能夠?qū)崿F(xiàn)完美平移。nice啊~
void CImageProcessingView::OnJhbhPy()
{
if(numPicture==0) {
AfxMessageBox("載入圖片后才能空間平移!",MB_OK,0);
return;
}
//定義采樣對話框也是用來空間變換平移的坐標(biāo)
CImagePYDlg dlg;
if( dlg.DoModal()==IDOK ) //顯示對話框
{
//采樣坐標(biāo)最初為圖片的自身像素
if( dlg.m_xPY>m_nWidth || dlg.m_yPY>m_nHeight ) {
AfxMessageBox("圖片平移不能為超過原圖長寬!",MB_OK,0);
return;
}
AfxMessageBox("圖片空間變換-平移!",MB_OK,0);
//打開臨時的圖片 讀寫文件
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
int num; //記錄每行多余的圖像素數(shù)個數(shù)
int sfSize; //補齊后的圖像大小
//重點:圖像的每行像素都必須是4的倍數(shù):1*1的圖像為 r g b 00H
if(m_nWidth*3%4!=0)
{
num=(4-m_nWidth*3%4);
sfSize=(m_nWidth*3+num)*m_nHeight; //每行多number個
}
else
{
num=0;
sfSize=m_nWidth*m_nHeight*3;
}
//注意:假如最后一行像素不足,我默認(rèn)處理為完整的一行,不足補00H
//總之處理后的圖像總是m*n且為4倍數(shù),每行都完整存在
/*更改文件頭信息 定義臨時文件頭結(jié)構(gòu)變量*/
BITMAPFILEHEADER bfhsf;
BITMAPINFOHEADER bihsf;
bfhsf=bfh;
bihsf=bih;
bfhsf.bfSize=sfSize+54;
fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
CString str;
str.Format("補齊=%d",num);
AfxMessageBox(str);
/*臨時變量存儲的像素與sfSize相同 new和delete有效的進(jìn)行動態(tài)內(nèi)存的分配和釋放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[sfSize];
int Place; //建立臨時坐標(biāo) 記錄起始坐標(biāo)(0,0)平移過來的位置
int m_pImagePlace; //原始圖像平移為(0,0) 圖像把它平移到Place位置
unsigned char black=0; //填充黑色='0'
unsigned char other=0; //補碼00H='\0'
Place=dlg.m_yPY*(m_nWidth*3+num); //前m_yPY行都要填充為黑色
m_pImagePlace=0; //圖像處事位置為(0,0),把該點像素平移過去
int countWidth=0; //記錄每行的像素個數(shù),滿行時變回0
int number=0; //數(shù)字記錄使用的像素行數(shù),平移時使用
for(int i=0 ; i<sfSize ; i++ )
{
/*第一部分:到平移后像素位置前面的所有像素點賦值為黑色*/
if(i<Place)
{
ImageSize[i]=black; //賦值為黑色
continue;
}
/*第二部分:平移區(qū)域的左邊部分賦值為黑色*/
else if(i>=Place && countWidth<dlg.m_xPY*3) //RGB乘3
{
ImageSize[i]=black; //賦值為黑色
countWidth++;
continue;
}
/*第三部分:圖像像素平移區(qū)域*/
else if(i>=Place && countWidth>=dlg.m_xPY*3)
{
ImageSize[i]=m_pImage[m_pImagePlace];
m_pImagePlace++;
countWidth++;
if(countWidth==m_nWidth*3)
{
if(num==0)
{
countWidth=0;
number++;
m_pImagePlace=number*m_nWidth*3;
}
else //num為補0
{
for(int j=0;j<num;j++)
{
i++;
ImageSize[i]=other;
}
countWidth=0;
number++;
m_pImagePlace=number*(m_nWidth*3+num); //重點:添加Num
}
}
}
}
fwrite(ImageSize,sfSize,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200; //200表示幾何變換
Invalidate();
}
}
? ? ? ? 運行效果如下圖所示,完美平移,其他算法遇到斜線問題類似補齊即可。
?
?
二. 圖像鏡像
1.水平鏡像翻轉(zhuǎn)
? ? ? ? 其變換矩陣如下:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?X=width-X0-1 ? (width為圖像寬度)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Y=Y0
? ? ? ? 打開類向?qū)?#xff0c;在CImageProcessingView中添加IDs為ID_JHBH_FZ,生成函數(shù),代碼如下:
/* 幾何變換 圖像翻轉(zhuǎn):自己對這個功能比較感興趣,做個圖像反轉(zhuǎn) */
void CImageProcessingView::OnJhbhFz()
{
if(numPicture==0) {
AfxMessageBox("載入圖片后才能空間反轉(zhuǎn)!",MB_OK,0);
return;
}
AfxMessageBox("圖片空間變換-反轉(zhuǎn)圖像!",MB_OK,0);
//打開臨時的圖片
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/*new和delete有效的進(jìn)行動態(tài)內(nèi)存的分配和釋放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage];
int countWidth=0; //記錄每行的像素個數(shù),滿行時變回0
int Place; //記錄圖像每行的位置,便于圖像反轉(zhuǎn)
int number=0; //數(shù)字記錄使用的像素行數(shù)
Place=m_nWidth*3-1;
//翻轉(zhuǎn)矩陣: y=y0 x=width-x0-1
for(int i=0 ; i<m_nImage ; i++ )
{
if(countWidth==m_nWidth*3)
{
countWidth=0;
}
ImageSize[i]=m_pImage[Place]; //(0,0)賦值(0,width*3-1)像素
Place--;
countWidth++;
if(countWidth==m_nWidth*3)
{
number++;
Place=number*m_nWidth*3-1;
}
}
fwrite(ImageSize,m_nImage,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200;
Invalidate();
}
? ? ? ? 運行效果如下圖所示,其中還是存在一些小BUG,如前面的BMP圖補0湊齊4整數(shù)倍寬度或顏色失幀。
?
2.垂直鏡像倒轉(zhuǎn)
? ? ? ? 其中變換矩陣如下:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??X=X0
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Y=height-Y0-1 ? (height為圖像高度)
? ? ? ? 它相當(dāng)于把原圖的像素矩陣的最后一行像素值賦值給第一行,首先找到(0,0)對應(yīng)的(height-1,0)像素值,然后依次賦值該行的像素數(shù)據(jù);最后當(dāng)前行賦值結(jié)束,依次下一行。重點是找到每行的第一個像素點即可。
? ? ? ? 代碼中引用兩個變量:Place=(m_nWidth*3)*(m_nHeight-1-1)即是(height-1,0)最后一行的第一個像素點;然后是循環(huán)中Place=(m_nWidth*3)*(m_nHeight-number-1)找到每行的第一個像素點。
? ? ? ? 同樣通過類向?qū)珊瘮?shù)void CImageProcessingView::OnJhbhDz(),代碼如下:
/* 幾何變換 圖像倒轉(zhuǎn) */
void CImageProcessingView::OnJhbhDz()
{
if(numPicture==0) {
AfxMessageBox("載入圖片后才能空間反轉(zhuǎn)!",MB_OK,0);
return;
}
AfxMessageBox("圖片空間變換-反轉(zhuǎn)圖像!",MB_OK,0);
//打開臨時的圖片
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/*new和delete有效的進(jìn)行動態(tài)內(nèi)存的分配和釋放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage];
int countWidth=0; //記錄每行像素個數(shù),滿行時變回0
int Place; //每列位置
int number=0; //像素行數(shù)
Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存儲
//翻轉(zhuǎn)矩陣: x=x0 y=height-y0-1
for(int i=0 ; i<m_nImage ; i++ )
{
ImageSize[i]=m_pImage[Place]; //(0,0)賦值(0,0)像素
Place++;
countWidth++;
if(countWidth==m_nWidth*3)
{
countWidth=0;
number++;
Place=(m_nWidth*3)*(m_nHeight-number-1);
}
}
fwrite(ImageSize,m_nImage,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200;
Invalidate();
}
? ? ? ? 運行結(jié)果如下圖所示,第二張圖顏色沒有失幀或變灰,這完全可以懷疑在翻轉(zhuǎn)過程中RGB像素編程BGR后導(dǎo)致的結(jié)果,最終實現(xiàn)了翻轉(zhuǎn)圖像,但灰度存在一定;所以如果改為RBG順序不變化即可原圖顏色顯示。
?
?
?
三. 圖像旋轉(zhuǎn)
? ? ? ? 圖像饒原點旋轉(zhuǎn)順時針theta角矩陣變換如下:注意BMP圖像(0,0)左下角
? ? ? ? 寫到這里真心覺得寫底層的代碼非常困難啊!尤其是以為像素轉(zhuǎn)換二維像素,同時也覺得當(dāng)時的自己算法部分還是很強大的,也感覺到如果采用GDI+操作像素矩陣Matrix或ColorMatrix是多么的方便,因為它定義好了X和Y向量,這就是為什么Android前面寫的圖像處理要容易得多。但是效率高~
? ? ? ? 好像利用GDI+旋轉(zhuǎn)通過幾句代碼即可:
? ? ? ? matrix.Rotate(15); //矩陣旋轉(zhuǎn)15度
? ? ? ? graph.SetTransform(&matrix);
? ? ? ? graph.DrawImage(&image,points,3);
? ? ? ? 下面這部分代碼是實現(xiàn)Android旋轉(zhuǎn)的:參考我的博客
//旋轉(zhuǎn)圖片
private void TurnPicture() {
Matrix matrix = new Matrix();
turnRotate=turnRotate+15;
//選擇角度 饒(0,0)點選擇 正數(shù)順時針 負(fù)數(shù)逆時針 中心旋轉(zhuǎn)
matrix.setRotate(turnRotate,bmp.getWidth()/2,bmp.getHeight()/2);
Bitmap createBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
Canvas canvas = new Canvas(createBmp);
Paint paint = new Paint();
canvas.drawBitmap(bmp, matrix, paint);
imageCreate.setBackgroundColor(Color.RED);
imageCreate.setImageBitmap(createBmp);
textview2.setVisibility(View.VISIBLE);
}
? ? ? ? 實現(xiàn)效果如下圖所示:
? ? ? ? 言歸正傳,新建Dialog如下圖所示,設(shè)置ID_DIALOG_XZ和變量:
? ? ? ? 再點擊空白處創(chuàng)建CImageXZDlg類(旋轉(zhuǎn)),它會自動生成.h和.cpp文件。打開類向?qū)蒀ImageXZDlg類的成員變量m_xzds(旋轉(zhuǎn)度數(shù)),并設(shè)置其為int型(最大值360 最小值0)。
? ? ? ? 在類向?qū)?Ctrl+W)選擇類CImageProcessingView,為ID_JHBH_TXXZ(圖像旋轉(zhuǎn))添加函數(shù),同時添加頭文件#include "ImageXZDlg.h"
/**********************************************************/
/* 幾何變換:圖片旋轉(zhuǎn)
/* 先添加對話框:IDD_JHBH_TXXZ(圖像旋轉(zhuǎn)),創(chuàng)建新類CImageXZDlg
/* 創(chuàng)建輸入度數(shù)的:m_xzds Member variables 為int 0-360間
/**********************************************************/
void CImageProcessingView::OnJhbhTxxz()
{
if(numPicture==0) {
AfxMessageBox("載入圖片后才能空間旋轉(zhuǎn)!",MB_OK,0);
return;
}
//定義對話框并調(diào)用對話框
CImageXZDlg dlg;
if( dlg.DoModal()==IDOK ) //顯示對話框
{
AfxMessageBox("圖片空間變換-旋轉(zhuǎn)圖像!",MB_OK,0);
//讀寫文件
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
/*new和delete有效的進(jìn)行動態(tài)內(nèi)存的分配和釋放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[m_nImage];
int Place; //記錄圖像每行的位置,便于圖像旋轉(zhuǎn)
/*定義PA=3.14時使用的方法是arcsin(1.0/2)*6即為π*/
double PA;
PA=asin(0.5)*6;
/*把輸入的0-360的正整數(shù)度數(shù)轉(zhuǎn)換為角度,30度=π/6*/
double degree;
degree=PA*dlg.m_xzds/180; //調(diào)用dlg.m_xzds(旋轉(zhuǎn)度數(shù))
//對應(yīng)的二維矩陣 注意圖像矩陣從左下角開始處理 它最終要轉(zhuǎn)換成一維存儲
int X,Y; //圖像變換前通過一維矩陣轉(zhuǎn)換為二維
int XPlace,YPlace;
//輸出轉(zhuǎn)換為的角度
CString str;
str.Format("轉(zhuǎn)換后的角度=%f",degree);
AfxMessageBox(str);
//圖像旋轉(zhuǎn)處理
for(int i=0 ; i<m_nImage ; i++ )
{
//原圖:一維矩陣轉(zhuǎn)換為二維矩陣
X=(i/3)%m_nWidth;
Y=(i/3)/m_nWidth;
//注意錯誤:X=i/m_nHeight Y=i%m_nWidth; 只輸出最后1/3
//圖像旋轉(zhuǎn)為:a(x,y)=x*cos-y*sin b(x,y)=x*sin+y*cos
XPlace=(int)(X*cos(degree)-Y*sin(degree));
YPlace=(int)(X*sin(degree)+Y*cos(degree));
//在轉(zhuǎn)換為一維圖想輸出
if( (XPlace>=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) )
{
Place=YPlace*m_nWidth*3+XPlace*3;
//在圖像范圍內(nèi)賦值為該像素
if(Place+2<m_nImage)
{
ImageSize[i]=m_pImage[Place];
i++;
ImageSize[i]=m_pImage[Place+1];
i++;
ImageSize[i]=m_pImage[Place+2];
}
//否則賦值為黑色
else
{
ImageSize[i]=0;
i++;
ImageSize[i]=0;
i++;
ImageSize[i]=0;
}
}
//否則賦值為黑色
else
{
ImageSize[i]=0;
i++;
ImageSize[i]=0;
i++;
ImageSize[i]=0;
}
}
fwrite(ImageSize,m_nImage,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200; //幾何變換
Invalidate();
}
}
? ? ? ? 運行效果如下圖所示,中心旋轉(zhuǎn)太難了!找到中心那個位置就不太容易,我做不下去了,fuck~同時旋轉(zhuǎn)過程中,由于是饒左下角(0,0)實現(xiàn),故有的角度會到界面外顯示全黑。下圖分別旋轉(zhuǎn)15度和355度。
?
?
?
四. 圖像縮放
? ? ? ? 圖像縮放主要有兩種方法:
? ? ? ? 1.最近鄰插值:向后映射時,輸出圖像的灰度等于離它所映射位置最近的輸入圖像的灰度值。其中向前映射和向后映射如下:
?
? ? ? ? 對于向前映射每個輸出圖像的灰度要經(jīng)過多次運算,對于向后映射,每個輸出圖像的灰度只經(jīng)過一次運算。在實際應(yīng)用中,更多的是采用向后映射法,其中根據(jù)四個相鄰像素灰度值計算某個位置的像素灰度值即為灰度級插值。
? ? ? ? 2.雙線性插值:四點確定一個平面函數(shù),屬于過約束問題。即單位正方形頂點已知,求正方形內(nèi)任一點的f(x,y)值。
? ? ? ? 換個通熟的說法,如下圖所示。采用最近鄰插值法就是P(x,y)像素值采用四舍五入等于離它最近的輸入圖像像素值。分別計算它到四個頂點之間的距離,但是這樣會造成圖像的馬賽克、鋸齒等現(xiàn)象。而采用雙線性插值法,主要通過該坐標(biāo)周圍的四個像素值,按照比例混合計算器近似值。比例混合的依據(jù)是離哪個像素近,哪個像素的比例越大。
? ? ? ? 下面是采用最近鄰插值法的過程,注意BMP圖縮放還需修改頭文件信息。
? ? ? ? 第一步:在資源視圖中添加“圖像縮放”Dialog
? ? ? ? 第二步:點擊空白處創(chuàng)建對話框的類CImageSFDlg,同時打開類向?qū)槠涮砑映蓡T變量m_sfbs(縮放倍數(shù)),其為int型在0-200之間。
? ? ? ? 第三步:打開類向?qū)槠涮砑映蓡T函數(shù)void CImageProcessingView::OnJhbhSf() 并實現(xiàn)縮放。同時添加頭文件#include "ImageSFDlg.h"。
/*******************************************************************/
/* ID_JHBH_SF: 幾何運算-縮放-最近鄰插值算法
/* 算法思想:輸出圖像的灰度等于離它所映射位置最近的輸入圖像的灰度值
/* 先計算出放大縮小后的長寬,根據(jù)它計算找原圖中的點灰度,四舍五入
/*******************************************************************/
void CImageProcessingView::OnJhbhSf()
{
if(numPicture==0) {
AfxMessageBox("載入圖片后才能幾何縮放圖像!",MB_OK,0);
return;
}
CImageSFDlg dlg; //定義縮放對話框
if( dlg.DoModal()==IDOK )
{
//采樣坐標(biāo)最初為圖片的自身像素 m_sfbs(縮放倍數(shù))
if( dlg.m_sfbs==0 ) {
AfxMessageBox("輸入圖片縮放倍數(shù)不能為0!",MB_OK,0);
return;
}
FILE *fpo = fopen(BmpName,"rb");
FILE *fpw = fopen(BmpNameLin,"wb+");
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
/*先求縮放后的長寬*/
int sfWidth,sfHeight; //縮放后的長寬
int sfSize; //縮放后的圖像大小
sfWidth=(int)(m_nWidth*(dlg.m_sfbs*1.0)/100); //24位圖像RGB必須是3倍數(shù) 循環(huán)讀取時為RGB
sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100);
int number; //記錄每行多余的圖像素數(shù)個數(shù)
//重點:圖像的每行像素都必須是4的倍數(shù):1*1的圖像為 r g b 00H
if(sfWidth*3%4!=0) {
number=(4-sfWidth*3%4);
sfSize=(sfWidth*3+(4-sfWidth*3%4))*sfHeight;
}
else {
number=0;
sfSize=sfWidth*sfHeight*3;
}
//注意:假如最后一行像素不足,我默認(rèn)處理為完整的一行,不足補00H
//總之處理后的圖像總是m*n且為4倍數(shù),每行都完整存在
/*更改文件頭信息 定義臨時文件頭結(jié)構(gòu)變量*/
BITMAPFILEHEADER bfhsf;
BITMAPINFOHEADER bihsf; //縮放(sf)
bfhsf=bfh;
bihsf=bih;
bfhsf.bfSize=sfSize+54;
bihsf.biWidth=sfWidth;
bihsf.biHeight=sfHeight;
//顯示部分m_nDrawWidth<650顯示原圖,否則顯示
flagSF=1; //圖像縮放為1標(biāo)識變量
m_nDrawWidthSF=sfWidth;
m_nDrawHeightSF=sfHeight;
fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
fread(m_pImage,m_nImage,1,fpo);
unsigned char red,green,blue;
unsigned char other=0; //補碼00H='\0'
int placeX; //記錄在原圖中的第幾行的位置
int placeY; //記錄在原圖中的位置(x,y)
int placeBH; //記錄變換后在變換圖中的位置
/*new和delete有效的進(jìn)行動態(tài)內(nèi)存的分配和釋放*/
unsigned char *ImageSize;
ImageSize=new unsigned char[sfSize];
/*讀取文件像素信息 縮放注意:1.找最近灰度 2.四舍五入法(算法+0.5)*/
for(int i=0; i<sfHeight ; i++ ) //行
{
placeX=(int)(i/(dlg.m_sfbs*1.0/100)+0.5)*bih.biWidth*3;
for(int j=0; j<sfWidth ; j++ ) //列
{
red=green=blue=0;
//放大倍數(shù)為(dlg.m_sfbs*1.0/100)
placeY=placeX+(int)(j/(dlg.m_sfbs*1.0/100)+0.5)*3;
//重點是:number*i補充00H,如果是numer圖像會被切成2塊
placeBH=(i*sfWidth*3+number*i)+j*3;
if(placeY+2<m_nImage)
{
ImageSize[placeBH]=m_pImage[placeY];
ImageSize[placeBH+1]=m_pImage[placeY+1];
ImageSize[placeBH+2]=m_pImage[placeY+2];
}
else
{
ImageSize[placeBH]=0;
ImageSize[placeBH+1]=0;
ImageSize[placeBH+2]=0;
}
}
}
fwrite(ImageSize,sfSize,1,fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level=200;
Invalidate();
}
}
? ? ? ? 第四步:因為圖像縮放修改BMP圖片頭信息,所以需要修改ShowBitmap中的顯示第二張圖片時的部分代碼。如下所示:添加變量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。
/*定義顯示圖像縮放時的長寬與標(biāo)記*/
int flagSF=0; //圖像幾何變換縮放變換
int m_nDrawWidthSF=0; //圖像顯示寬度縮放后
int m_nDrawHeightSF=0; //圖像顯示高度縮放后
//****************顯示BMP格式圖片****************//
void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
{
......
else //圖像幾何變換
if(level=200)
{
m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
if( m_bitmap.m_hObject ) {
m_bitmap.Detach(); //m_bitmap為創(chuàng)建的位圖對象
}
m_bitmap.Attach(m_hBitmapChange);
//定義并創(chuàng)建一個內(nèi)存設(shè)備環(huán)境
CDC dcBmp;
if( !dcBmp.CreateCompatibleDC(pDC) ) //創(chuàng)建兼容性的DC
return;
BITMAP m_bmp; //臨時bmp圖片變量
m_bitmap.GetBitmap(&m_bmp); //將圖片載入位圖中
CBitmap *pbmpOld = NULL;
dcBmp.SelectObject(&m_bitmap); //將位圖選入臨時內(nèi)存設(shè)備環(huán)境
//圖片顯示調(diào)用函數(shù)StretchBlt
if(flagSF==1)
{
CString str;
str.Format("縮放長=%d 寬%d 原圖長=%d 寬=%d",m_nDrawWidthSF,
m_nDrawHeightSF,m_nWidth,m_nHeight);
AfxMessageBox(str);
flagSF=0;
//m_nDrawWidthSF縮放此存見函數(shù)最近鄰插值法中賦值
if(m_nDrawWidthSF<650 && m_nDrawHeightSF<650)
pDC->StretchBlt(m_nWindowWidth-m_nDrawWidthSF,0,
m_nDrawWidthSF,m_nDrawHeightSF,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
else
pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); //顯示大小為640*640
}
else {
//如果圖片太大顯示大小為固定640*640 否則顯示原圖大小
if(m_nDrawWidth<650 && m_nDrawHeight<650)
pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,
m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
else
pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
}
//恢復(fù)臨時DC的位圖
dcBmp.SelectObject(pbmpOld);
}
? ? ? ? 運行效果如下圖所示,采用最近鄰插值法縮放大了會出現(xiàn)失幀。
?
?
? ? ? ? 但是同時當(dāng)圖片縮小是總是報錯,圖片縮放確實有點難,因為像素需要補齊4整數(shù)倍,同時需要修改消息頭,同時像素矩陣的變換都非常復(fù)雜。
?
? ? ? ??
//
轉(zhuǎn)載:https://blog.csdn.net/eastmount/article/details/46345299
總結(jié)
以上是生活随笔為你收集整理的MFC空间几何变换之图像平移、镜像、旋转、缩放的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Leetcode unique-path
- 下一篇: 写论文文献引用方式