圖像的縮小從物理意義上來說,就是將圖像的每個像素的大小縮小相應的倍數。但是,改變像素的物理尺寸顯然不是那么容易的,從數字圖像處理的角度來看,圖像的縮小實際就是通過減少像素個數來實現的。顯而易見的,減少圖像的像素會造成圖像信息丟失。為了在縮小圖像的同時,保持原圖的概貌特征不丟失,從原圖中選擇的像素方法是非常重要的。本文主要介紹基於等間隔采樣的圖像縮小和基於局部均值的圖像縮小以及其在OpenCV2的實現。
基於等間隔采樣的圖像縮小
這種圖像縮小算法,通過對原圖像像素進行均勻采樣來保持所選擇到的像素仍舊可以反映原圖像的概貌特征。
算法描述
設原圖的大小為W*H,寬度和長度的縮小因子分別為看k1和k2,那么采樣間隔為:W/k1,W/k2.也就是說在原圖的水平方向每隔W/k1,在垂直方向每隔W/k2取一個像素。長和寬的縮小因子k1和k2相等時,圖像時等比例縮小,不等時是不等比例縮小,縮小圖像的長和寬的比例會發生變化。
基於OpenCV的算法實現
void scaleIntervalSampling(const Mat &src, Mat &dst, double xRatio, double yRatio) { //只處理uchar型的像素 CV_Assert(src.depth() == CV_8U); // 計算縮小后圖像的大小 //沒有四舍五入,防止對原圖像采樣時越過圖像邊界 int rows = static_cast<int>(src.rows * xRatio); int cols = static_cast<int>(src.cols * yRatio); dst.create(rows, cols, src.type()); const int channesl = src.channels(); switch (channesl) { case 1: //單通道圖像 { uchar *p; const uchar *origal; for (int i = 0; i < rows; i++){ p = dst.ptr<uchar>(i); //四舍五入 //+1 和 -1 是因為Mat中的像素是從0開始計數的 int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1; origal = src.ptr<uchar>(row); for (int j = 0; j < cols; j++){ int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1; p[j] = origal[col]; //取得采樣像素 } } break; } case 3://三通道圖像 { Vec3b *p; const Vec3b *origal; for (int i = 0; i < rows; i++) { p = dst.ptr<Vec3b>(i); int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1; origal = src.ptr<Vec3b>(row); for (int j = 0; j < cols; j++){ int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1; p[j] = origal[col]; //取得采樣像素 } } break; } } }
代碼實現還是比較簡單的,需要注意的一點就是對於單通道和三通道圖像用ptr取行地址時,使用的數據類型的不同。三通道圖像中,一個像素有BGR三個分量,可以使用Vec3b來保存。
運行結果
基於局部均值的圖像縮小
算法描述
等間隔采樣的縮小方法實現簡單,但是原圖像中未被選中的像素信息會在縮小后的圖像中丟失。局部均值的圖像縮小方法對其進行了改進。在求縮小圖像的像素時,不僅僅單純的取在原圖像中的采樣點像素,而是以相鄰的兩個采樣點為分割,將原圖像分成一個個的子塊。縮小圖像的像素取相應子塊像素的均值。
根據局部均值縮小的原理:g11 = (f11 + f12 + f21 + f22 ) / 4
基於OpenCV的算法實現
void scalePartAverage(const Mat &src, Mat &dst, double xRatio, double yRatio) { int rows = static_cast<int>(src.rows * xRatio); int cols = static_cast<int>(src.cols * yRatio); dst.create(rows, cols, src.type()); int lastRow = 0; int lastCol = 0; Vec3b *p; for (int i = 0; i < rows; i++) { p = dst.ptr<Vec3b>(i); int row = static_cast<int>((i + 1) / xRatio + 0.5) - 1; for (int j = 0; j < cols; j++) { int col = static_cast<int>((j + 1) / yRatio + 0.5) - 1; Vec3b pix; average(src, Point_<int>(lastRow, lastCol), Point_<int>(row, col), pix); p[j] = pix; lastCol = col + 1; //下一個子塊左上角的列坐標,行坐標不變 } lastCol = 0; //子塊的左上角列坐標,從0開始 lastRow = row + 1; //子塊的左上角行坐標 } }
算法實現只考慮了三通道圖像,單通道圖像與之類似。
局部均值縮小圖片實現的關鍵點在子塊左上角行和列坐標的求取(子塊右下角行和列坐標,就是間隔采樣的采樣點)。圖像子塊求出后,計算出子塊像素的平均值即是縮小圖像的像素。
子塊平均像素的求解
void average(const Mat &img, Point_<int> a, Point_<int> b, Vec3b &p) { const Vec3b *pix; Vec3i temp; for (int i = a.x; i <= b.x; i++){ pix = img.ptr<Vec3b>(i); for (int j = a.y; j <= b.y; j++){ temp[0] += pix[j][0]; temp[1] += pix[j][1]; temp[2] += pix[j][2]; } } int count = (b.x - a.x + 1) * (b.y - a.y + 1); p[0] = temp[0] / count; p[1] = temp[1] / count; p[2] = temp[2] / count; }
求取局部平均值時,要注意數據類型。Vec3b實際就元素類型為uchar的Vector,也就是說Vec3b的每一個分量的最大值是255.而求均值時,需要累加像素值,使用uchar時會越界,為此,這里使用Vec3i作為中間值保存。Vec3i是int型的Vector。
運行結果
兩種方法的比較
上面兩幅圖像都是512 * 512的lenna圖片縮小到0.3,左邊為使用局部均值的算法,右邊是使用等間隔采樣的算法。
下一章准備介紹下OpenCV下實現幾種常見的圖像放大方法。





