轉自:http://blog.csdn.net/xizero00/article/details/6631209
數字圖像基本處理算法
xizero00
常熟理工學院(CIT) 計算機科學與工程學院 下一代互聯網實驗室(NGIL Lab)
Email:xizero00@163.com
由於SIFT算法需要用到很多算法,所以這段時間研究了一下一些最基本的圖像處理算法,
好了,廢話不多說,先看結果,接下來上圖說話:
1.二值化:
圖1 二值化(閾值:140)處理效果
所謂二值化簡單一點講,就是將圖像划分成黑和白,通過設定一個標准如果大於這個標准就設為白,如果小於這個標准,就設為黑,而這個標准,就叫做閾值。
具體定義如下所示:
下面給出實現的代碼:
//二值化 //函數的參數iTR為閾值 void CBMPSampleDlg::ThresholdProcess(int iTR) { //讀取BMP文件 m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("二值化.bmp");
//將像素指針定位到圖像數據的開始 RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; //獲得圖像的大小 int iSize = m_Dib.GetSizeImage(); //BMP文件頭指針 BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; //遍歷每一個像素,並判斷每一個像素的分量(RGB),將其與閾值比較,然后進行賦值 for(int i = 0; i < iSize/( pBmiHeader->biBitCount / 8); i++) { if ( (rgbtri[i].rgbtRed < iTR )| (rgbtri[i].rgbtGreen < iTR) | (rgbtri[i].rgbtBlue < iTR) ) { rgbtri[i].rgbtRed = (BYTE) 0; rgbtri[i].rgbtGreen = (BYTE) 0; rgbtri[i].rgbtBlue = (BYTE) 0; } else { rgbtri[i].rgbtRed = (BYTE) 255; rgbtri[i].rgbtGreen = (BYTE) 255; rgbtri[i].rgbtBlue = (BYTE) 255;
} } //顯示圖像 DrawPic(); }
|
代碼中,首先會讀取原始圖像文件,文件的格式為BMP的,關於BMP圖像的存儲結構,在接下來的文章中會講到。
在讀取圖像之后,會將指針定位到圖像像素數據的開始位置,然后獲得圖像的大小,然后通過BMP文件頭獲得圖像的一個像素所占據的二進制的位數,這樣就知道一個像素由幾個字節組成的了,需要注意的是,一個像素不一定是由三個字節組成的,比如是灰度圖像其只需要一個字節來存儲一個像素究竟是灰到什么程度其范圍在0-255 之間,而彩色圖像卻是由三種顏色組成的也就是所說的三原色RGB分別為Red、Green、Blue三種顏色組成,這三種顏色每個分量各占一個字節,所以這里需要三個字節,另外在BMP圖像中還一個結構為RGBQUAD的結構體,這里一個像素占據的是4個字節,其實,這里就涉及到了8位圖像24位圖像以及32位圖像的問題了,所謂的8位圖像其實,每一個像素占一個字節,24位圖像,每一個像素占據3個字節、而32位圖像每一個像素占據4個字節就是這么來的。
2.海報化
圖2 海報化處理效果
所謂的海報化其實就是將每一個像素的分量與224進行與運算,而244的16進制表示可以表示成0xe0,前面介紹了一個像素的分量的范圍在0-255范圍內,所以只需要將這兩個數值的二進制位相與即可完成海報化的處理效果。
下面為實現的具體代碼:
//海報化 void CBMPSampleDlg::Posterize() { m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("海報化.bmp"); RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; int iSize = m_Dib.GetSizeImage(); BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; for(int i = 0; i < iSize/( pBmiHeader->biBitCount / 8); i++) { rgbtri[i].rgbtRed = (BYTE) (rgbtri[i].rgbtRed & 0xe0); rgbtri[i].rgbtGreen = (BYTE) (rgbtri[i].rgbtGreen & 0xe0); rgbtri[i].rgbtBlue = (BYTE) (rgbtri[i].rgbtBlue & 0xe0); } DrawPic(); } |
上面的這段代碼是我參考DirectShow里面的ezrgb24濾鏡這個例子改寫的,另外下面的灰度化也是采用里面的改寫的。
3.灰度化
圖3 灰度化處理效果
灰度化有很多種處理方法,有分量法、最大值法、平均值法以及加權平均值法。
1)分量法
將彩色圖像中的三分量的亮度作為三個灰度圖像的灰度值,可根據應用需要選取一種灰度圖像。
f1(i,j)=R(i,j) f2(i,j)=G(i,j)f3(i,j)=B(i,j)
其中fk(i,j)(k=1,2,3)為轉換后的灰度圖像在(i,j)處的灰度值。
2)最大值法
將彩色圖像中的三分量亮度的最大值作為灰度圖的灰度值。
f(i,j)=max(R(i,j),G(i,j),B(i,j))
3) 平均值法
將彩色圖像中的三分量亮度求平均得到一個灰度圖。
f(i,j)=(R(i,j)+G(i,j)+B(i,j)) /3
4) 加權平均法
根據重要性及其它指標,將三個分量以不同的權值進行加權平均。由於人眼對綠色的敏感最高,對藍色敏感最低,因此,按下式對RGB三分量進行加權平均能得到較合理的灰度圖像。
f(i,j)=0.30R(i,j)+0.59G(i,j)+0.11B(i,j))
在我們的程序中,我們采用的是加權平均法進行灰度化。
下面為實現的代碼:
//灰度化 void CBMPSampleDlg::ConvertToGray() { m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("灰度化.bmp"); RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; int iSize = m_Dib.GetSizeImage(); BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; int iGrayvalue = 0; //遍歷每一個像素 for(int i = 0; i < iSize/( pBmiHeader->biBitCount / 8); i++) { iGrayvalue = int( rgbtri[i].rgbtBlue * 0.11 + rgbtri[i].rgbtGreen * 0.59 + rgbtri[i].rgbtRed * 0.3 ); rgbtri[i].rgbtRed = (BYTE) iGrayvalue; rgbtri[i].rgbtGreen = (BYTE) iGrayvalue; rgbtri[i].rgbtBlue = (BYTE) iGrayvalue; } DrawPic();
} |
在上述代碼中,通過遍歷每一個像素,然后計算該像素的三個分量的加權平均值,將三個分量設置成同一個值,這樣就實現了對圖像的灰度化處理。
4.模糊化
圖4 模糊化處理效果
其實所謂的模糊化,就是將各個像素的相鄰的像素的各個分量的值相加,然后除以2就可以實現對圖像的模糊處理。
下面給出代碼:
//模糊化 void CBMPSampleDlg::Blur() { m_Dib.AttachMapFile("1.bmp", TRUE); m_Dib.CopyToMapFile("模糊化.bmp"); RGBTRIPLE *rgbtri = (RGBTRIPLE *)m_Dib.m_lpImage; int iSize = m_Dib.GetSizeImage(); BITMAPINFOHEADER * pBmiHeader = (BITMAPINFOHEADER *)m_Dib.m_lpBMIH; LONG lHeight = pBmiHeader->biHeight; LONG lWidth = pBmiHeader->biWidth; for (int y = 0 ; y < lHeight; y++) { for (int x = 2 ; x < lWidth; x++, rgbtri ++) { rgbtri->rgbtRed = (BYTE) ((rgbtri->rgbtRed + rgbtri[2].rgbtRed) >> 1); rgbtri->rgbtGreen = (BYTE) ((rgbtri->rgbtGreen + rgbtri[2].rgbtGreen) >> 1); rgbtri->rgbtBlue = (BYTE) ((rgbtri->rgbtBlue + rgbtri[2].rgbtBlue) >> 1); } rgbtri +=2; } DrawPic(); } |
上面的代碼同樣是遍歷每一個像素將前一個像素和后一個像素相加,然后將獲得的值右移一位,這樣就能實現除以2的效果,之所以做位運算,是因為位運算的速度比除法運算要快很多。
在介紹縮放算法之前,先介紹一些基本的概念
圖像放大算法
圖像放大有許多算法,其關鍵在於對未知像素使用何種插值方式。以下我們將具體分析幾種常見的算法,然后從放大后的圖像是否存在色彩失真,圖像的細節是否得到較好的保存,放大過程所需時間是否分配合理等多方面來比較它們的優劣。
當把一個小圖像放大的時候,比如放大400%,我們可以首先依據原來的相鄰4個像素點的色彩值,按照放大倍數找到新的ABCD像素點的位置並進行對應的填充,但是它們之間存在的大量的像素點,比如p點的色彩值卻是不可知的,需要進行估算。
圖5原始圖像的相鄰4個像素點分布圖
圖6 圖像放大4倍后已知像素分布圖
1)最臨近點插值算法(Nearest Neighbor)
最鄰近點插值算法是最簡單也是速度最快的一種算法,其做法是將放大后未知的像素點P,將其位置換算到原始影像上,與原始的鄰近的4周像素點A,B,C,D做比較,令P點的像素值等於最靠近的鄰近點像素值即可。如上圖中的P點,由於最接近D點,所以就直接取P=D。
這種方法會帶來明顯的失真。在A,B中點處的像素值會突然出現一個跳躍,這就是出現馬賽克和鋸齒等明顯走樣的原因。最臨近插值法唯一的優點就是速度快。
2)雙線性插值算法(Bilinear Interpolation)
其做法是將放大后未知的像素點P,將其位置換算到原始影像上,計算的四個像素點A,B,C,D對P點的影響(越靠近P點取值越大,表明影響也越大),其示意圖如下。
圖7 雙線性插值算法示意圖
其具體的算法分三步:
第一步插值計算出AB兩點對P點的影響得到e點的值。
圖8 線性插值算法求值示意圖
對線性插值的理解是這樣的,對於AB兩像素點之間的其它像素點的色彩值,認定為直線變化的,要求e點處的值,只需要找到對應位置直線上的點即可。換句話說,A,B間任意一點的值只跟A,B有關。
第二步,插值計算出CD兩點對P點的影響得到f點的值。
第三步,插值計算出ef兩點對P點的影響值。
雙線性插值算法由於插值的結果是連續的,所以視覺上會比最鄰近點插值算法要好一些,不過運算速度稍微要慢一點,如果講究速度,是一個不錯的折衷。
3)雙立方插值算法(Bicubic Interpolation)
雙立方插值算法與雙線性插值算法類似,對於放大后未知的像素點P,將對其影響的范圍擴大到鄰近的16個像素點,依據對P點的遠近影響進行插值計算,因P點的像素值信息來自16個鄰近點,所以可得到較細致的影像,不過速度比較慢。
圖 9雙立方插值的附近4個臨近點
好了,在介紹完了這些基礎知識后,我們接下來講解如何實現這些算法。
5.最臨近點插值縮放
圖10 最鄰近點插值放大(2倍)處理效果
(由於Word版面原因,您看到的圖像被word自動縮放成合適的寬度了)
最臨近插值的的思想很簡單。對於通過反向變換得到的的一個浮點坐標,對其進行簡單的取整,得到一個整數型坐標,這個整數型坐標對應的像素值就是目的像素的像素值,也就是說,取浮點坐標最鄰近的左上角點(對於DIB是右上角,因為它的掃描行是逆序存儲的)對應的像素值。可見,最鄰近插值簡單且直觀,但得到的圖像質量不高。
下面給出算法的實現:
void CBMPSampleDlg::NearestNeighbourInterpolation(float fWRatio, float fHRatio) { //打開舊圖像 CDib srcDib; srcDib.AttachMapFile("1.bmp", TRUE);
//獲取舊的高和寬 LONG lOldHeight = srcDib.m_lpBMIH->biHeight; LONG lOldWidth = srcDib.m_lpBMIH->biWidth;
//保存舊的圖像的字節 WORD wOldBitCount = srcDib.m_lpBMIH->biBitCount;
//計算新的高和寬 //四舍五入 LONG lNewHeight = LONG ( lOldHeight * fHRatio + 0.5 ); LONG lNewWidth = LONG ( lOldWidth * fWRatio + 0.5 );
CSize size; size.cx = lNewWidth; size.cy = lNewHeight;
//創建圖像 m_Dib.CreatDib( size, wOldBitCount );
//獲取舊圖像每行字節數 LONG lOldLineBytes = WIDTHBYTES( lOldWidth * wOldBitCount ); //獲取新圖像每行的字節數 LONG lNewLineBytes = WIDTHBYTES( lNewWidth * wOldBitCount );
//計算填充的字節 int iNewFillBytes = lNewLineBytes - lNewWidth * ( wOldBitCount / 8 ); int iOldFillBytes = lOldLineBytes - lOldWidth * ( wOldBitCount / 8 );
//指定圖像大小,新圖像的大小有不同 m_Dib.m_dwSizeImage = lNewLineBytes * lNewHeight; //分配空間 m_Dib.AllocImage();
//定位到數據的開頭,也就是圖像最后一行的行首 LPRGBTRIPLE DRgb = ( LPRGBTRIPLE ) m_Dib.m_lpImage; LPRGBTRIPLE SRgb = ( LPRGBTRIPLE ) srcDib.m_lpImage;
//將每行的頭指針存儲起來 PRGBTRIPLE *pNewRow = (PRGBTRIPLE *)malloc(sizeof(PRGBTRIPLE) * lNewHeight); PRGBTRIPLE * pOldRow = (PRGBTRIPLE *)malloc(sizeof(PRGBTRIPLE) * lOldHeight);
LONG srcCol = 0,srcRow = 0; for (int row = 0 ; row < lNewHeight ; row ++) { pNewRow[row] = PRGBTRIPLE( (PBYTE)DRgb + row * lNewLineBytes ); }
for (int row = 0 ; row < lOldHeight ; row ++) { pOldRow[row] = PRGBTRIPLE ( (PBYTE)SRgb + row * lOldLineBytes ); }
for ( LONG row = 0 ; row < lNewHeight ; row ++ ) { //對每一行進行處理 for ( LONG col = 0 ; col < lNewWidth ; col ++ ) { //計算在源圖像中的坐標srcCol 和srcRow //四舍五入 srcCol = (LONG) floor(col / fWRatio); srcRow = (LONG) floor(row / fHRatio);
//判斷計算出來的坐標是否在源圖像中 if ( ( ( srcCol >= 0 ) && ( srcCol < lOldWidth ) ) && ( ( srcRow >= 0 ) && ( srcRow < lOldHeight ) ) ) { //定位指針到源圖像的位置 //復制像素的值 *(pNewRow[row] + col) = *(pOldRow[srcRow] + srcCol); } } } //釋放申請的內存 free( pNewRow ); free( pOldRow );
DrawPic(); m_Dib.CopyToMapFile("最鄰近法插值.bmp"); } |
圖中的fWRatio, fHRatio分別為水平方向和垂直方向的縮放系數,如果大於1則為放大,如果大於0小於1則為縮小。
6.雙線性插值縮放
圖11 雙線性插值放大(2倍)處理效果
(由於Word版面原因,您看到的圖像被word自動縮放成合適的寬度了)
下面給出雙線性插值的具體實現:
//雙線性插值法 void CBMPSampleDlg::BiLinearInterpolation(float fWRatio, float fHRatio) {
//打開舊圖像 CDib srcDib; srcDib.AttachMapFile("1.bmp", TRUE);
//獲取舊的高和寬 LONG lOldHeight = srcDib.m_lpBMIH->biHeight; LONG lOldWidth = srcDib.m_lpBMIH->biWidth;
//保存舊的圖像的字節 WORD wOldBitCount = srcDib.m_lpBMIH->biBitCount;
//計算新的高和寬 //四舍五入 LONG lNewHeight = LONG ( lOldHeight * fHRatio + 0.5 ); LONG lNewWidth = LONG ( lOldWidth * fWRatio + 0.5 );
CSize size; size.cx = lNewWidth; size.cy = lNewHeight;
//創建圖像 m_Dib.CreatDib( size, wOldBitCount );
//獲取舊圖像每行字節數 LONG lOldLineBytes = WIDTHBYTES( lOldWidth * wOldBitCount ); //獲取新圖像每行的字節數 LONG lNewLineBytes = WIDTHBYTES( lNewWidth * wOldBitCount );
//計算填充的字節 int iNewFillBytes = lNewLineBytes - lNewWidth * ( wOldBitCount / 8 ); int iOldFillBytes = lOldLineBytes - lOldWidth * ( wOldBitCount / 8 );
//指定圖像大小,新圖像的大小有不同 m_Dib.m_dwSizeImage = lNewLineBytes * lNewHeight; //分配空間 m_Dib.AllocImage();
//定位到數據的開頭,也就是圖像最后一行的行首 LPRGBTRIPLE DRgb = ( LPRGBTRIPLE ) m_Dib.m_lpImage; LPRGBTRIPLE SRgb = ( LPRGBTRIPLE ) srcDib.m_lpImage;
//將每行的頭指針存儲起來
PRGBTRIPLE *pNewRow = (PRGBTRIPLE *)malloc( sizeof(PRGBTRIPLE) * lNewHeight ); PRGBTRIPLE * pOldRow = (PRGBTRIPLE *)malloc( sizeof(PRGBTRIPLE) * lOldHeight );
for (int row = 0 ; row < lNewHeight ; row ++) { pNewRow[row] = PRGBTRIPLE( (PBYTE)DRgb + row * lNewLineBytes ); }
for (int row = 0 ; row < lOldHeight ; row ++) { pOldRow[row] = PRGBTRIPLE ( (PBYTE)SRgb + row * lOldLineBytes ); }
float p = 0 , q = 0; LONG PACol = 0,PARow = 0; PRGBTRIPLE PA = NULL ,PB = NULL , PC = NULL , PD = NULL , PDst = NULL; for ( LONG row = 0 ; row < lNewHeight ; row ++ ) { //對每一行進行處理 for ( LONG col = 0 ; col < lNewWidth ; col ++ ) { //計算在源圖像中的坐標
PACol = (LONG) floor( col / fWRatio ); PARow = (LONG) floor( row / fHRatio );
//計算P和Q p = col / fWRatio - PACol; q = row / fHRatio - PARow;
//判斷計算出來的坐標是否在源圖像中 if ( ( ( PACol >= 0 ) && ( PACol < lOldWidth -1 ) ) && ( ( PARow >= 0 ) && ( PARow < lOldHeight -1 ) ) ) { //獲得四周的像素指針
//PA即 指向A點的指針,如下圖為各個點的分布 // A | C // q // -p Dst // // B D // PA = pOldRow[PARow] + PACol; PB = pOldRow[PARow + 1] + PACol; PC = pOldRow[PARow] + PACol + 1; PD = pOldRow[PARow + 1] + PACol + 1;
//需要確定的像素的指針,在目標圖像中 PDst = pNewRow[row] + col;
//對目標像素的分量進行計算
//Blue PDst->rgbtBlue = BYTE( ( 1 - p ) * ( 1 - q ) * PA->rgbtBlue \ + ( 1 - p ) * q * PB->rgbtBlue \ + p * ( 1- q ) * PC->rgbtBlue \ + p * q * PD->rgbtBlue ); if(PDst->rgbtBlue < 0) { PDst->rgbtBlue = 0; } if (PDst->rgbtBlue > 255) { PDst->rgbtBlue = 255; }
//Green PDst->rgbtGreen = BYTE( ( 1 - p ) * ( 1 - q ) * PA->rgbtGreen \ + ( 1 - p ) * q * PB->rgbtGreen \ + p * ( 1- q ) * PC->rgbtGreen \ + p * q * PD->rgbtGreen ); if(PDst->rgbtGreen < 0) { PDst->rgbtGreen = 0; } if (PDst->rgbtGreen > 255) { PDst->rgbtGreen = 255; }
//Red PDst->rgbtRed = BYTE( ( 1 - p ) * ( 1 - q ) * PA->rgbtRed \ + ( 1 - p ) * q * PB->rgbtRed \ + p * ( 1- q ) * PC->rgbtRed \ + p * q * PD->rgbtRed ); if(PDst->rgbtRed < 0) { PDst->rgbtRed = 0; } if (PDst->rgbtRed > 255) { PDst->rgbtRed = 255; }
} } }
free( pNewRow ); free( pOldRow );
DrawPic(); m_Dib.CopyToMapFile("雙線性插值法.bmp"); } |
7.高斯模糊
圖12高斯模糊處理效果
這里的高斯平滑(模糊)或者說濾波器就是這樣一種帶權的平均濾波器. 那么這些權重如何分布呢? 我們先來看幾個經典的模板例子:
嘗試了使用這些濾波器對我們原來的圖進行操作, 得到了這樣的一組結果:
圖13 原圖
圖14 3x3 高斯模板處理效果
圖15 5x5 高斯模板處理效果
單純從效果來看, 兩個模板都起到了平滑的作用, 只是程度有深淺的區分. 那么從理論上來說為什么能起到平滑的作用呢? 很顯然, 像素的顏色不僅由自身決定了, 同時有其周圍的像素加權決定, 客觀上減小了和周圍像素的差異. 同時這些權重的設定滿足了越近權重越大的規律. 從理論來講, 這些權重的分布滿足了著名的所謂高斯分布:
這就是1維的計算公式
這就是2維的計算公式
x, y表示的就是當前點到對應點的距離, 而那些具體的模板就是由這里公式中的一些特例計算而來. 需要說明的是不只有這么一些特例, 從wikipedia可以方便地找到那些復雜的模板。
下面給出實現的代碼:
void CBMPSampleDlg::GaussianSmooth() { CDib srcDib; srcDib.AttachMapFile("1.bmp", TRUE); srcDib.CopyToMapFile("高斯模糊.bmp");
//獲取舊的高和寬 LONG lOldHeight = srcDib.m_lpBMIH->biHeight; LONG lOldWidth = srcDib.m_lpBMIH->biWidth;
//保存舊的圖像的字節 WORD wOldBitCount = srcDib.m_lpBMIH->biBitCount;
//獲取舊圖像每行字節數 LONG lOldLineBytes = WIDTHBYTES( lOldWidth * wOldBitCount );
PRGBTRIPLE * pOldRow = (PRGBTRIPLE *)malloc( sizeof(PRGBTRIPLE) * lOldHeight );
LPRGBTRIPLE SRgb = ( LPRGBTRIPLE ) srcDib.m_lpImage; //計算每一行的起點指針 for (int row = 0 ; row < lOldHeight ; row ++) { pOldRow[row] = PRGBTRIPLE ( (PBYTE)SRgb + row * lOldLineBytes ); }
//高斯模板 5 * 5 int templates[25] = { 1, 4, 7, 4, 1, 4, 16, 26, 16, 4, 7, 26, 41, 26, 7, 4, 16, 26, 16, 4, 1, 4, 7, 4, 1 };
//各個分量的和 int Bsum = 0 , GSum = 0 , RSum = 0;
//在高斯模板中的索引 int index = 0;
//用到的臨時指針 PRGBTRIPLE PTemp = NULL , PDst = NULL;
//在模板中的值 int s = 0;
//計算在5*5的高斯模板中的目標像素RGB的值 for ( int row =2 ; row <lOldHeight - 2 ; row++ ) { for ( int col =2 ; col < lOldWidth - 2 ; col++ ) { Bsum = GSum = RSum = 0; index = 0;
for ( int m = row - 2 ; m < row + 3 ; m++) { for (int n=col - 2 ; n < col + 3 ; n++) { PTemp = pOldRow[m] + n; s = templates[index ++];
//Blue Bsum += PTemp->rgbtBlue * s;
//Green GSum += PTemp->rgbtGreen * s;
//Red RSum += PTemp->rgbtRed * s;
} }
Bsum /= 273; GSum /= 273; RSum /= 273;
//判斷計算出來的值是否合法 //如果超過了255則設成255 //依次對BGR進行檢查 if (Bsum > 255) { Bsum = 255; }
if (GSum > 255) { GSum = 255; }
if (RSum > 255) { RSum = 255; }
PDst = pOldRow[row] + col; PDst->rgbtBlue = Bsum; PDst->rgbtGreen = GSum; PDst->rgbtRed = RSum; } }
DrawPic();
} |
代碼中給出的是5*5的高斯模板實現的高斯模糊。由於時間原因,就先粗略地講到這里。
好了一下子給出這么多算法,需要一點時間消化一下,我會把實現的工程放在我的CSDN上,方便大家下載。敬請期待正在醞釀的《SIFT算法原理與實現》,此篇算是對SIFT算法的一些關鍵的算法進行介紹吧。
如果您有什么看不懂的,可以提出您的意見。
Email:xizero00#163.com
將#換成@就是我的郵箱,廣交八方朋友!
另外在寫這篇文章的時候也參閱了不少文獻和博客,在此一並感謝他們的無私的付出,只有每一個人的分享才能讓大家有所收獲!謝謝你們!
這里把工程的文件放到了CSDN上供感興趣的童鞋們下載