圖形的縮放的需求很普遍也很好理解,就是圖形的放大和縮小
圖形縮放的算法主要分為兩種 :
- 最近鄰插值算法
- 雙線性插值算法
最近鄰插值算法
算法原理:先確定縮放系數(shù),再將目標圖形的坐標映射到源圖形的坐標,最后取得相應(yīng)的像素點像素值
比如:我需要將一幅400x400 的圖形方法到 800*800,可知放大系數(shù)
Hscale = 800/400 = 2;
Vscale = 800/400 =2;
對于放大的后的圖形中的任意一點,都可以映射到源圖形中:
比如:求放大后的圖形中坐標為 300,300處的像素值為:
point_srcx = point_dstx/Hscale = 300/2 = 150
point_srcy = point_dsty/Vscale = 300/2 = 150
循環(huán)映射所有的像素點就可以完成圖形的放大和縮小

代碼實現(xiàn)如下:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
bool _nearestScaleOperate(Mat& src, Mat& dst, float Hscale, float Vscale)
{
int32_t dst_Lines = round(src.rows*Hscale);
int32_t dst_Columns = round(src.cols*Vscale);
dst = Mat(dst_Lines, dst_Columns, src.type());
if (src.channels() == 1) {
for (int32_t i = 0; i < dst_Lines; i++) {
for (int32_t j = 0; j < dst_Columns; j++) {
int32_t i_index = round(i / Hscale);
int32_t j_index = round(j / Vscale);
if (i_index > src.rows - 1)
i_index = src.rows - 1;
if (j_index > src.cols - 1)
j_index = src.cols - 1;
dst.at<uint8_t>(i, j) = src.at<uint8_t>(i_index, j_index);
}
}
}
// RGB channel 彩色圖形的處理
else {
for (int32_t i = 0; i < dst_Lines; i++) {
for (int32_t j = 0; j < dst_Columns; j++) {
int32_t i_index = round(i / Hscale);
int32_t j_index = round(j / Vscale);
if (i_index > src.rows - 1)
i_index = src.rows - 1;
if (j_index > src.cols - 1)
j_index = src.cols - 1;
dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i_index, j_index)[0];
dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i_index, j_index)[1];
dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i_index, j_index)[2];
}
}
}
return true;
}
bool testnearestScale()
{
cout << "nearest scale demo" << endl;
const char *ImageName = "./demoImage.png";
Mat img = imread(ImageName, 1);
if (img.empty()) {
cout << "read image empty" << endl;
return false;
}
Mat dstimg;
_nearestScaleOperate(img, dstimg, 0.5, 0.5);
imshow("src_image", img);
imshow("dst_image", dstimg);
return true;
}
運行效果如下:

算法局限性:
最近鄰插值算法是一種最基本最簡單的圖形插值算法,效果也最不好,放大圖像后存在馬賽克,縮小后圖形也存在失真
原因是當由目標圖形的坐標反推得到的源圖的的坐標是一個浮點數(shù)的時候,采用了四舍五入的方法,直接采用了和這個浮點數(shù)最接近的象素的值,這種方法是很不科學(xué)的,當推得坐標值為 0.75的時候,不應(yīng)該就簡單的取為1,既然是0.75,比1要小0.25 ,比0要大0.75
雙線性插值算法
基于上述的最近鄰插值算法的局限性,目標象素值其實應(yīng)該根據(jù)這個源圖中虛擬的點四周的四個真實的點來按照一定的規(guī)律計算出來的,這樣才能達到更好的縮放效果
雙線型內(nèi)插值算法就是這種圖像縮放算法,它充分的利用了源圖中虛擬點四周的四個真實存在的像素值來共同決定目標圖中的一個像素值,因此縮放效果比簡單的最鄰近插值要好很多。
基本原理:
對于一個目的像素,設(shè)置坐標通過縮放系數(shù)變換得到的浮點坐標為(i+u,j+v) (其中i、j均為浮點坐標的整數(shù)部分,u、v為浮點坐標的小數(shù)部分,是取值[0,1)區(qū)間的浮點數(shù)),
這個像素得值 f(i+u,j+v) 可由原圖像中坐標為 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所對應(yīng)的周圍四個像素的值決定
公式為:
f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)


算法代碼優(yōu)化
- 幾何中心對齊
求目標圖形變換到源圖形的坐標:
point_srcX = point_dstX/Hscale
point_srcY = point_dstY/Vscale
優(yōu)化為:
point_srcX = (point_dstX + 0.5)/Hscale -0.5
point_srcY = ( point_dstY + 0.5)/Vscale -0.5
這么做的目的是為了幾何中心對齊的需要:
比如將 33 的圖形放大為 99 我們希望縮放的中心為 坐標(4,4) 映射(1,1)
但是如果不進行中心對齊
Hscale = Vscale = 3
point_srcX=4/3 = 1.333
point_srcY=4/3 = 1.333
中心對齊后:
point_srcX=4+0.5/3 - 0.5= 1
point_srcY=4+0.5/3 - 0.5 = 1
符合我們的需求:
雙線性插值是線性插值在二維時的推廣,在兩個方向上共做了三次線性插值

-
先計算 A點 C點的值,此時不考慮 Y軸方向的值,只考慮 X 軸上相鄰兩個點的相關(guān)性
A_C計算.png 再計算 B 點的值,此時不考慮X軸方向的值,只考慮 Y 軸上相鄰兩個點 A和C

opencv 實現(xiàn)算法代碼如下:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>
using namespace std;
using namespace cv;
bool _BilinearInterOperate(Mat& src, Mat& dst, float Hscale, float Vscale)
{
int32_t dst_Lines = round(src.rows*Hscale);
int32_t dst_Columns = round(src.cols*Vscale);
dst = Mat(dst_Lines, dst_Columns, src.type());
for (int32_t i = 0; i < dst_Lines; i++) {
// 幾何中心對齊
double i_index = (i + 0.5) / Hscale - 0.5;
//防止邊界溢出
if (i_index < 0)
i_index = 0;
if (i_index >= src.rows -1)
i_index = src.rows - 2;
// i1 是向上取整后的值
// i2 是向下取整
uint32_t i1 = floor(i_index);
uint32_t i2 = ceil(i_index);
float u = i_index - i1;
for (int32_t j = 0; j < dst_Columns; j++) {
// 幾何中心對齊
double j_index = (j + 0.5) / Vscale - 0.5;
//防止邊界溢出
if (j_index < 0)
j_index = 0;
if (j_index >= src.cols - 1)
j_index = src.cols - 2;
uint32_t j1 = floor(j_index);
uint32_t j2 = ceil(j_index);
double v = j_index - j1;
if (src.channels() == 1) {
dst.at<uint8_t>(i, j) = \
(1 - u)*(1 - v)*src.at<uint8_t>(i1, j1) + (1 - u)*v*src.at<uint8_t>(i1, j2)\
+ u*(1 - v)*src.at<uint8_t>(i2, j1) + u*v*src.at<uint8_t>(i2, j2);
} else {
dst.at<Vec3b>(i, j)[0] = \
(1 - u)*(1 - v)*src.at<Vec3b>(i1, j1)[0] + (1 - u)*v*src.at<Vec3b>(i1, j2)[0]\
+ u*(1 - v)*src.at<Vec3b>(i2, j1)[0] + u*v*src.at<Vec3b>(i2, j2)[0];
dst.at<Vec3b>(i, j)[1] = \
(1 - u)*(1 - v)*src.at<Vec3b>(i1, j1)[1] + (1 - u)*v*src.at<Vec3b>(i1, j2)[1]\
+ u*(1 - v)*src.at<Vec3b>(i2, j1)[1] + u*v*src.at<Vec3b>(i2, j2)[1];
dst.at<Vec3b>(i, j)[2] = \
(1 - u)*(1 - v)*src.at<Vec3b>(i1, j1)[2] + (1 - u)*v*src.at<Vec3b>(i1, j2)[2]\
+ u*(1 - v)*src.at<Vec3b>(i2, j1)[2] + u*v*src.at<Vec3b>(i2, j2)[2];
}
}
}
return true;
}
bool testBilinearInter() {
cout << "bilinearInter scale demo" << endl;
const char *ImageName = "C:/Users/86185/Documents/Visual Studio 2015/Projects/opencvdemo/x64/Debug/demoImage.png";
Mat img = imread(ImageName, 1);
if (img.empty()) {
cout << "read image empty" << endl;
return false;
}
Mat dstimg;
_BilinearInterOperate(img, dstimg, 1.2, 1.2);
imshow("src_image", img);
imshow("dst_image", dstimg);
return true;
}
顯示效果:

