【數字圖像處理】六.MFC空間幾何變換之圖像平移、鏡像、旋轉、縮放具體解釋


       本文主要講述基於VC++6.0 MFC圖像處理的應用知識,主要結合自己大三所學課程《數字圖像處理》及課件進行解說,主要通過MFC單文檔視圖實現顯示BMP圖片空間幾何變換。包含圖像平移、圖形旋轉、圖像反轉倒置鏡像和圖像縮放的知識。

同一時候文章比較具體基礎。沒有採用GDI+獲取矩陣。而是通過讀取BMP圖片信息頭和矩陣像素實現變換,希望該篇文章對你有所幫助,尤其是剛開始學習的人和學習圖像處理的學生。


       【數字圖像處理】一.MFC具體解釋顯示BMP格式圖片
       【數字圖像處理】二.MFC單文檔切割窗體顯示圖片
       【數字圖像處理】三.MFC實現圖像灰度、採樣和量化功能具體解釋
       【數字圖像處理】四.MFC對話框繪制灰度直方圖
       【數字圖像處理】五.MFC圖像點運算之灰度線性變化、灰度非線性變化、閾值化和均衡化處理具體解釋
        免費資源下載地址:
        http://download.csdn.net/detail/eastmount/8772951


一. 圖像平移

       前一篇文章講述了圖像點運算(基於像素的圖像變換),這篇文章講述的是圖像幾何變換:在不改變圖像內容的情況下對圖像像素進行空間幾何變換的處理方式。


        點運算對單幅圖像做處理,不改變像素的空間位置;代數運算對多幅圖像做處理。也不改變像素的空間位置;幾何運算對單幅圖像做處理,改變像素的空間位置,幾何運算包含兩個獨立的算法:空間變換算法和灰度級插值算法。

        空間變換操作包含簡單空間變換、多項式卷繞和幾何校正、控制柵格插值和圖像卷繞,這里主要講述簡單的空間變換。如圖像平移、鏡像、縮放和旋轉。主要是通過線性代數中的齊次坐標變換。


        圖像平移坐標變換例如以下:


        執行效果例如以下圖所看到的,當中BMP圖片(0,0)像素點為左下角。


        其代碼核心算法:
        1.在對話框中輸入平移坐標(x,y) m_xPY=x。m_yPY=y
        2.定義Place=dlg.m_yPY*m_nWidth*3 表示當前m_yPY行須要填充為黑色
        3.新建一個像素矩陣 ImageSize=new unsigned char[m_nImage]
        4.循環整個像素矩陣處理 
             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) {//圖像像素平移區域
                        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號)

        第二步:設置平移對話框。

將試圖切換到ResourceView界面--選中Dialog,右鍵鼠標新建一個Dialog,並新建一個名為IDD_DIALOG_PY。編輯框(X)IDC_EDIT_PYX 和 (Y)IDC_EDIT_PYY。確定為默認button。設置成下圖對話框:


        第三步:在對話框資源模板空白區域雙擊鼠標—Create a new class創建一個新類--命名為CImagePYDlg。

會自己主動生成它的.h和.cpp文件。

打開類向導(Ctrl W),選擇類名:CImagePYDlg加入成員變量例如以下圖所看到的。同一時候在Message Maps中生成ID_JHBH_PY實現函數。

 
        第四步:在CImageProcessingView.cpp中加入頭文件#include "ImagePYDlg.h",並實現平移。
/********************************************************/
/* 圖像空間幾何變換:圖像平移 ID_JHBH_PY(幾何變換-平移) 
/* 使用平移對話框:CImagePYDlg dlg                     
/* 算法:f(x,y)=f(x+x0,y+y0)圖像全部點平移,空的補黑'0' 
/* 注意該圖像平移方法僅僅是從左上角(0,0)處開始平移        
/* 其它方向原理同樣 自己去實現                           
/********************************************************/

void CImageProcessingView::OnJhbhPy() 
{
	if(numPicture==0) {
		AfxMessageBox("加載圖片后才干空間平移!",MB_OK,0);
		return;
	}
	//定義採樣對話框也是用來空間變換平移的坐標
	CImagePYDlg dlg;     
	if( dlg.DoModal()==IDOK ) //顯示對話框
	{
		//採樣坐標最初為圖片的自身像素
		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);

		/************************************************************/
		/* 圖片空間變換-平移                                        
		/* 坐標(dlg.m_xPY,dlg.m_yPY)表示圖像平移的坐標        
		/* 先用Plave計算出平移后的起始坐標。其它的坐標賦值為'0'黑色 
		/* 然后依次平移坐標,空的賦為黑色,否則填充                 
		/************************************************************/
		
		/******************************************************************/
		/* 嚴重錯誤1:數組變量賦值相等                                    
		/* 在View.h中定義變量 BYTE *m_pImage 讀入圖片數據后的指針         
		/* 建立暫時變量數組,讓它平移變換 unsigned char *ImageSize         
		/* ImageSize=m_pImage(錯誤)                                       
		/* 會導致ImageSize賦值變換時m_pImage也產生了變換,所以輸出全為黑色 
		/*     由於它倆指向了同樣的數組地址                               
		/* 解決方法:使用以下C++的new方法動態分配或for循環i=m_nImage賦值  
		/******************************************************************/

		/*暫時變量存儲的像素與m_pImage同樣。便於處理圖像*/
		unsigned char *ImageSize;      
	    ImageSize=new unsigned char[m_nImage];  //new和delete有效的進行動態內存的分配和釋放

		int Place;                    //建立暫時坐標 記錄起始坐標(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++ )                         
		/* 不能使用的上面的由於可能圖像的最后一行沒有完整的一行像素 
		/* 這樣會出現exe報錯,使用m_nImage讀寫全部像素比較正確       
		/************************************************************/

		Place=dlg.m_yPY*m_nWidth*3;   //前m_yPY行都要填充為黑色          
		black=0;                       //顏色為黑色
		m_pImagePlace=0;               //圖像處事位置為(0,0),把該點像素平移過去 
		int countWidth=0;              //記錄每行的像素個數,滿行時變回0
		int number=0;                  //數字記錄使用的像素行數,平移時使用

		for(int i=0 ; i<m_nImage ; i++ )
		{
			/*假設每行的像素填滿時清為0*/
			if(countWidth==m_nWidth*3) {
				countWidth=0;
			}
			
			/*第一部分:到平移后像素位置前面的全部像素點賦值為黑色*/
			if(i<Place) {
				ImageSize[i]=black;     //賦值為黑色
				continue;
			}
			
			/*第二部分:平移區域的左邊部分賦值為黑色*/
			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為黑色   
			/****************************/
		
			/*第三部分:圖像像素平移區域*/
			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標記又一次繪制圖片,代碼例如以下:
else        //圖像幾何變換
if(level=200)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
       執行時須要注意一點:BMP圖像在處理過程中可能會出現一些斜線,而平移(40,60)位移量時可能出現例如以下。他是由於BMP格式有個很重要的規定,要求每一掃描的字節數據必須能被4整除,也就是Dword對齊(長度4字節),假設圖像的一行字節數不能被4整除,就須要在每行末尾不起0達到標准。


        比如一行像素為97字節。我們就須要補3個字節嗎,數值能夠是0,可是我們在BMP格式的信息頭里說明了其寬度。所以補齊后對我們沒有影響,所以后面補若干個字節的0就可以直到被4整除。


 
        通過后面的圖像縮放后,我從學做了一遍這個補齊的縮放。

代碼例如以下,可以實現完美平移。nice啊~

void CImageProcessingView::OnJhbhPy() 
{
	if(numPicture==0) {
		AfxMessageBox("加載圖片后才干空間平移!",MB_OK,0);
		return;
	}
	//定義採樣對話框也是用來空間變換平移的坐標
	CImagePYDlg dlg;     
	if( dlg.DoModal()==IDOK ) //顯示對話框
	{
		//採樣坐標最初為圖片的自身像素
		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;            //記錄每行多余的圖像素數個數
		int sfSize;         //補齊后的圖像大小
		//重點:圖像的每行像素都必須是4的倍數: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;
		}
		//注意:假如最后一行像素不足,我默認處理為完整的一行,不足補00H
		//總之處理后的圖像總是m*n且為4倍數,每行都完整存在

		/*更改文件頭信息 定義暫時文件頭結構變量*/
		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有效的進行動態內存的分配和釋放*/
		unsigned char *ImageSize;      
	    ImageSize=new unsigned char[sfSize];  

		int Place;                    //建立暫時坐標 記錄起始坐標(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;                 //記錄每行的像素個數,滿行時變回0
		int number=0;                     //數字記錄使用的像素行數,平移時使用

		for(int i=0 ; i<sfSize ; i++ )  
        {  
            /*第一部分:到平移后像素位置前面的全部像素點賦值為黑色*/  
            if(i<Place) 
			{  
                ImageSize[i]=black;     //賦值為黑色  
                continue;  
            }  
              
            /*第二部分:平移區域的左邊部分賦值為黑色*/  
            else if(i>=Place && countWidth<dlg.m_xPY*3)  //RGB乘3
			{   
                ImageSize[i]=black;     //賦值為黑色  
                countWidth++;  
                continue;  
            }  
          
            /*第三部分:圖像像素平移區域*/  
            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.水平鏡像翻轉
        其變換矩陣例如以下:
                                 X=width-X0-1   (width為圖像寬度)
                                 Y=Y0

        打開類向導,在CImageProcessingView中加入IDs為ID_JHBH_FZ。生成函數。代碼例如以下:

/* 幾何變換 圖像翻轉:自己對這個功能比較感興趣,做個圖像反轉 */
void CImageProcessingView::OnJhbhFz() 
{
	if(numPicture==0) {
		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);
	
	/*new和delete有效的進行動態內存的分配和釋放*/
	unsigned char *ImageSize;      
	ImageSize=new unsigned char[m_nImage];    
	int countWidth=0;        //記錄每行的像素個數,滿行時變回0
	int Place;               //記錄圖像每行的位置,便於圖像反轉
	int number=0;            //數字記錄使用的像素行數
	Place=m_nWidth*3-1;

	//翻轉矩陣: 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整數倍寬度或顏色失幀。





2.垂直鏡像倒轉
        當中變換矩陣例如以下:
                                      X=X0
                                      Y=height-Y0-1   (height為圖像高度)
        它相當於把原圖的像素矩陣的最后一行像素值賦值給第一行,首先找到(0,0)相應的(height-1,0)像素值,然后依次賦值該行的像素數據。最后當前行賦值結束,依次下一行。重點是找到每行的第一個像素點就可以。
        代碼中引用兩個變量:Place=(m_nWidth*3)*(m_nHeight-1-1)即是(height-1,0)最后一行的第一個像素點;然后是循環中Place=(m_nWidth*3)*(m_nHeight-number-1)找到每行的第一個像素點。
        相同通過類向導生成函數void CImageProcessingView::OnJhbhDz(),代碼例如以下:
/* 幾何變換 圖像倒轉 */
void CImageProcessingView::OnJhbhDz() 
{
	if(numPicture==0) {
		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);
	
	/*new和delete有效的進行動態內存的分配和釋放*/
	unsigned char *ImageSize;      
	ImageSize=new unsigned char[m_nImage];    
	int countWidth=0;                   //記錄每行像素個數,滿行時變回0
	int Place;                          //每列位置
	int number=0;                       //像素行數
	Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存儲

	//翻轉矩陣: 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();
}
        執行結果例如以下圖所看到的,第二張圖顏色沒有失幀或變灰,這全然能夠懷疑在翻轉過程中RGB像素編程BGR后導致的結果。終於實現了翻轉圖像,但灰度存在一定。所以假設改為RBG順序不變化就可以原圖顏色顯示。






三. 圖像旋轉

        圖像饒原點旋轉順時針theta角矩陣變換例如以下:注意BMP圖像(0,0)左下角


        寫到這里真心認為寫底層的代碼非常困難啊!

尤其是以為像素轉換二維像素,同一時候也認為當時的自己算法部分還是非常強大的,也感覺到假設採用GDI+操作像素矩陣Matrix或ColorMatrix是多么的方便,由於它定義好了X和Y向量,這就是為什么Android前面寫的圖像處理要easy得多。

可是效率高~
        好像利用GDI+旋轉通過幾句代碼就可以:
        matrix.Rotate(15); //矩陣旋轉15度
        graph.SetTransform(&matrix);
        graph.DrawImage(&image,points,3);
        以下這部分代碼是實現Android旋轉的:參考我的博客

//旋轉圖片  
private void TurnPicture() {  
    Matrix matrix = new Matrix();  
    turnRotate=turnRotate+15;  
    //選擇角度 饒(0,0)點選擇 正數順時針 負數逆時針 中心旋轉  
    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);  
}
        實現效果例如以下圖所看到的:


        言歸正傳,新建Dialog例如以下圖所看到的,設置ID_DIALOG_XZ和變量:

        再點擊空白處創建CImageXZDlg類(旋轉),它會自己主動生成.h和.cpp文件。

打開類向導生成CImageXZDlg類的成員變量m_xzds(旋轉度數),並設置其為int型(最大值360 最小值0)。


        在類向導(Ctrl+W)選擇類CImageProcessingView,為ID_JHBH_TXXZ(圖像旋轉)加入函數。同一時候加入頭文件#include "ImageXZDlg.h"

/**********************************************************/
/* 幾何變換:圖片旋轉                                 
/* 先加入對話框:IDD_JHBH_TXXZ(圖像旋轉),創建新類CImageXZDlg  
/* 創建輸入度數的:m_xzds Member variables 為int 0-360間 
/**********************************************************/

void CImageProcessingView::OnJhbhTxxz() 
{
	if(numPicture==0) {
		AfxMessageBox("加載圖片后才干空間旋轉!",MB_OK,0);
		return;
	}
	
	//定義對話框並調用對話框
	CImageXZDlg dlg;    
	if( dlg.DoModal()==IDOK ) //顯示對話框
	{
		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);
		
		/*new和delete有效的進行動態內存的分配和釋放*/
		unsigned char *ImageSize;      
		ImageSize=new unsigned char[m_nImage];    
		int Place;          //記錄圖像每行的位置,便於圖像旋轉

		/*定義PA=3.14時使用的方法是arcsin(1.0/2)*6即為π*/
		double PA;
		PA=asin(0.5)*6;
		
		/*把輸入的0-360的正整數度數轉換為角度,30度=π/6*/
		double degree; 
		degree=PA*dlg.m_xzds/180;   //調用dlg.m_xzds(旋轉度數)
		
		//相應的二維矩陣 注意圖像矩陣從左下角開始處理 它終於要轉換成一維存儲
		int X,Y;               //圖像變換前通過一維矩陣轉換為二維
		int XPlace,YPlace;

		//輸出轉換為的角度
		CString str;
		str.Format("轉換后的角度=%f",degree);
		AfxMessageBox(str);

		//圖像旋轉處理
		for(int i=0 ; i<m_nImage ; i++ )
		{
			//原圖:一維矩陣轉換為二維矩陣
			X=(i/3)%m_nWidth;
			Y=(i/3)/m_nWidth;
			//注意錯誤:X=i/m_nHeight Y=i%m_nWidth; 僅僅輸出最后1/3

			//圖像旋轉為: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));
			
			//在轉換為一維圖想輸出
			if( (XPlace>=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) )
			{
				Place=YPlace*m_nWidth*3+XPlace*3;
				//在圖像范圍內賦值為該像素
				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();
	}
}
        執行效果例如以下圖所看到的,中心旋轉太難了!找到中心那個位置就不太easy,我做不下去了。fuck~同一時候旋轉過程中,因為是饒左下角(0,0)實現,故有的角度會到界面外顯示全黑。下圖分別旋轉15度和355度。






四. 圖像縮放

        圖像縮放主要有兩種方法:
        1.近期鄰插值:
向后映射時。輸出圖像的灰度等於離它所映射位置近期的輸入圖像的灰度值。

當中向前映射和向后映射例如以下:

 

        對於向前映射每一個輸出圖像的灰度要經過多次運算。對於向后映射。每一個輸出圖像的灰度僅僅經過一次運算。在實際應用中,很多其它的是採用向后映射法,當中依據四個相鄰像素灰度值計算某個位置的像素灰度值即為灰度級插值。


        2.雙線性插值:四點確定一個平面函數。屬於過約束問題。即單位正方形頂點已知,求正方形內任一點的f(x,y)值。




        換個通熟的說法。例如以下圖所看到的。

採用近期鄰插值法就是P(x,y)像素值採用四舍五入等於離它近期的輸入圖像像素值。分別計算它到四個頂點之間的距離,可是這樣會造成圖像的馬賽克、鋸齒等現象。而採用雙線性插值法。主要通過該坐標周圍的四個像素值,依照比例混合計算器近似值。

比例混合的根據是離哪個像素近,哪個像素的比例越大。



        以下是採用近期鄰插值法的過程。注意BMP圖縮放還需改動頭文件信息。
        第一步:在資源視圖中加入“圖像縮放”Dialog

        第二步:點擊空白處創建對話框的類CImageSFDlg,同一時候打開類向導為其加入成員變量m_sfbs(縮放倍數),其為int型在0-200之間。



        第三步:打開類向導為其加入成員函數void CImageProcessingView::OnJhbhSf() 並實現縮放。同一時候加入頭文件#include "ImageSFDlg.h"。
/*******************************************************************/
/* ID_JHBH_SF: 幾何運算-縮放-近期鄰插值算法               
/* 算法思想:輸出圖像的灰度等於離它所映射位置近期的輸入圖像的灰度值 
/* 先計算出放大縮小后的長寬,依據它計算找原圖中的點灰度,四舍五入  
/*******************************************************************/

void CImageProcessingView::OnJhbhSf() 
{
	if(numPicture==0) {
		AfxMessageBox("加載圖片后才干幾何縮放圖像!",MB_OK,0);
		return;
	}

	CImageSFDlg dlg;           //定義縮放對話框
	if( dlg.DoModal()==IDOK )
	{
		//採樣坐標最初為圖片的自身像素  m_sfbs(縮放倍數)
		if( dlg.m_sfbs==0 ) {
			AfxMessageBox("輸入圖片縮放倍數不能為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倍數 循環讀取時為RGB
		sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100);
		int number;                                      //記錄每行多余的圖像素數個數

		//重點:圖像的每行像素都必須是4的倍數: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;
		}
		//注意:假如最后一行像素不足,我默認處理為完整的一行,不足補00H
		//總之處理后的圖像總是m*n且為4倍數,每行都完整存在
	
		/*更改文件頭信息 定義暫時文件頭結構變量*/
		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標識變量
		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有效的進行動態內存的分配和釋放*/
		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;
				//放大倍數為(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。
/*定義顯示圖像縮放時的長寬與標記*/
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為創建的位圖對象
		}
		m_bitmap.Attach(m_hBitmapChange);
		//定義並創建一個內存設備環境
		CDC dcBmp;
		if( !dcBmp.CreateCompatibleDC(pDC) )   //創建兼容性的DC
			return;
		BITMAP m_bmp;                          //暫時bmp圖片變量
		m_bitmap.GetBitmap(&m_bmp);            //將圖片加載位圖中
		CBitmap *pbmpOld = NULL;
		dcBmp.SelectObject(&m_bitmap);         //將位圖選入暫時內存設備環境

		//圖片顯示調用函數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縮放此存見函數近期鄰插值法中賦值
			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); 
		}
		//恢復暫時DC的位圖
		dcBmp.SelectObject(pbmpOld);    
}
        執行效果例如以下圖所看到的,採用近期鄰插值法縮放大了會出現失幀。




        可是同一時候當圖片縮小是總是報錯。圖片縮放確實有點難,由於像素須要補齊4整數倍,同一時候須要改動消息頭,同一時候像素矩陣的變換都很復雜。


        最后還是希望文章對你有所幫助。假設文章有不足或錯誤之處。請海涵。自己給自己點個贊。挺不easy的,但還會繼續寫完~
      (By:Eastmount 2015-06-04 下午5點   http://blog.csdn.net/eastmount/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM