本文要实现的功能是使用最近邻插值以及双线性插值完成bmp图像的缩放。
1、最近邻插值
不需要计算,在待求象素的四邻象素中,将距离待求象素最近的邻象素灰度赋给待求象素:
设i+u, j+v(i, j为正整数, u, v为大于零小于1的小数,下同)为待求象素坐标,则待求象素灰度的值 f(i+u, j+v);如果(i+u, j+v)落在A区,即u<0.5, v<0.5,则将左上角象素的灰度值赋给待求象素,同理,落在B区则赋予右上角的象素灰度值,落在C区则赋予左下角象素的灰度值,落在D区则赋予右下角象素的灰度值。
最邻近算法计算量较小,但可能会造成插值生成的图像灰度上的不连续,在灰度变化的地方可能出现明显的锯齿状。
代码如下:
void image_scaling_nearest()
{
char readPath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\lunpan.bmp";
readBmp(readPath);
unsigned char *imagedata = NULL; //动态分配存储原图片的像素信息的二维数组
unsigned char *imagedataScal = NULL;//动态分配存储缩放后的图片的像素信息的二维数组
imagedata = pBmpBuf;
float ExpScalValue = 0; ////期望的缩放倍数(允许小数)
int FloatToIntwidth, FloatToIntheight;/////小数变成整数(float To Int)
int RotateAngle = 90;//要缩放的角度,默认90
//图片缩放处理
cout << "请输入要缩放的倍数:" << endl;
cin >> ExpScalValue;
///如果ExpScalValue含有小数,需要整数化
///对期望的缩放结果取整
FloatToIntwidth = (int)(ExpScalValue*bmpWidth);
FloatToIntheight = (int)(ExpScalValue*bmpHeight);
//图像每一行的字节数必须是4的整数倍
int lineByte2 = (FloatToIntwidth * biBitCount / 8 + 3) / 4 * 4;
imagedataScal = new unsigned char[lineByte2 * FloatToIntheight];///为缩放后图像分配存储空间
int pre_i, pre_j, after_i, after_j;//缩放前后对应的像素点坐标
for (int i = 0; i= 0 && pre_i < bmpHeight && pre_j >= 0 && pre_j < bmpWidth)//在原图范围内
*(imagedataScal + i * lineByte2 + j*3+k) = *(imagedata + pre_i * bmpWidth*3 + pre_j*3+k);
}
}
}
//保存bmp图片
char writePath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\111.bmp";
saveBmp(writePath, imagedataScal, FloatToIntwidth, FloatToIntheight, biBitCount, pColorTable);
printf("缩放变换完成,请查看Scalresult.bmp文件。\n\n");
//释放内存
delete[] imagedata;
delete[] imagedataScal;
}
2、双线性插值
双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值,线性插值的结果与插值的顺序无关。(下图从https://blog.csdn.net/zhangla1220/article/details/41014541截图所得)
目标图(x, y) 映射到原图是(X + u, Y + v)(计算方法同最邻近插值)。设u与v分别为X + u,Y + v的小数部分。由于下标都是整数,因此原图其实并不存在该点。则取其附近四个领域点为(X, Y) (X, Y + 1) (X + 1, Y) (X + 1, Y + 1),则目标图(x, y)处的值为 f(x , y) = f(X + u, Y + v) =f (X, Y) * (1 - u) * (1 - v) + f(X, Y + 1) * (1 - u) * v + f(X + 1, Y) * u * (1 - v) + f (X + 1, Y + 1) * u * v;
为更加形象地说明该原理,我们给出下图:
如上面所言,我们需要算出目标图像像素跟原图像像素的映射关系,我们从遍历目标图像像素点开始,对每一个像素点进行比例计算,算出其如果投影到原图像应该是处于原图像的哪个位置,通常所得到的结果正如上图的所示,求得的点P处于原图像的四个真实像素点中。
我们假定原图像像素点间的RGB(或灰度)值是呈线性变化的,则我们可以通过A、B两点RGB(或灰度)值算出AB线段上面的E点的RGB(或灰度)值,同理亦可算出F点的RGB(或灰度)值。得到了E、F两点的RGB(或灰度)值后可经由相同的方法得到P点的RGB(或灰度)值。到此,我们就知道该如何通过映射关系去求得目标图像的RGB(或灰度)值了。
我们把点A、B、C、D、E、F、P的RGB(或灰度)值分别记为F_A、F_B、F_C、F_D、F_E、F_F、F_P(注意由于RGB是三个值,这个记法其实不严谨,可以理解为是FA等是RGB的其中一个值,然后另外两个也可同理得到),则有
F_E=(1-0.7)*F_A+(1-0.3)*F_B,
F_F=(1-0.7)*F_C+(1-0.3)*F_D,
最终 得到目标点的值:
F_P=(1-0.7)*F_E+(1-0.3)*F_F=0.3*0.3*F_A+0.3*0.7*F_B+0.7*0.3*F_C+0.7*0.7*F_D。
代码如下:
void image_scaling_doubleline()
{
char readPath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\lunpan.bmp";
readBmp(readPath);
unsigned char *imagedata ; //动态分配存储原图片的像素信息的二维数组
unsigned char *imagedataScal ;//动态分配存储缩放后的图片的像素信息的二维数组
imagedata = pBmpBuf;
float ExpScalValue = 0; ////期望的缩放倍数(允许小数)
int FloatToIntwidth, FloatToIntheight;/////小数变成整数(float To Int)
int RotateAngle = 90;//要缩放的角度,默认90
//图片缩放处理
cout << "请输入要缩放的倍数:" << endl;
cin >> ExpScalValue;
///如果ExpScalValue含有小数,需要整数化
///对期望的缩放结果取整
FloatToIntwidth = (int)(ExpScalValue*bmpWidth);
FloatToIntheight = (int)(ExpScalValue*bmpHeight);
//图像每一行的字节数必须是4的整数倍
int lineByte = (bmpWidth * biBitCount / 8 + 3) / 4 * 4;
int lineByte2 = (FloatToIntwidth * biBitCount / 8 + 3) / 4 * 4;
imagedataScal = new unsigned char[lineByte2 * FloatToIntheight];///为缩放后图像分配存储空间
/*******************图像处理部分******************/
/*******************双线性插值******************/
for (int i = 0; i < FloatToIntheight; i++)
for (int j = 0; j < FloatToIntwidth; j++)
{
float d_original_img_hnum = i / ExpScalValue;
float d_original_img_wnum = j / ExpScalValue;
int i_original_img_hnum = d_original_img_hnum;
int i_original_img_wnum = d_original_img_wnum;
float distance_to_a_x = d_original_img_wnum - i_original_img_wnum;//在原图像中与a点的水平距离
float distance_to_a_y = d_original_img_hnum - i_original_img_hnum;//在原图像中与a点的垂直距离
int original_point_a = i_original_img_hnum*lineByte + i_original_img_wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点A
int original_point_b = i_original_img_hnum* lineByte + (i_original_img_wnum + 1) * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点B
int original_point_c = (i_original_img_hnum + 1)* lineByte + i_original_img_wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点C
int original_point_d = (i_original_img_hnum + 1)* lineByte + (i_original_img_wnum + 1) * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点D
if (i_original_img_hnum == bmpHeight - 1)
{
original_point_c = original_point_a;
original_point_d = original_point_b;
}
if (i_original_img_wnum == bmpWidth - 1)
{
original_point_b = original_point_a;
original_point_d = original_point_c;
}
int pixel_point = i*lineByte2 + j * 3;//映射尺度变换图像数组位置偏移量
for (int k = 0; k < 3; k++)
{
imagedataScal[pixel_point + k] =
imagedata[original_point_a + k] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
imagedata[original_point_b + k ]* distance_to_a_x*(1 - distance_to_a_y) +
imagedata[original_point_c + k ]* distance_to_a_y*(1 - distance_to_a_x) +
imagedata[original_point_d + k ]* distance_to_a_y*distance_to_a_x;
/*assert((pixel_point + k)<(lineByte2 * FloatToIntheight));
assert((original_point_a + k)<(lineByte * bmpHeight));
assert((original_point_b + k)<(lineByte * bmpHeight));
assert((original_point_c + k)<(lineByte * bmpHeight));
assert((original_point_d + k)<(lineByte * bmpHeight));*/
}
}
/*******************双线性插值******************/
/*******************图像处理部分******************/
//保存bmp图片
char writePath[] = "D:\\C++_file\\image_deal_C++\\IMAGE_JIEQU\\11-1.bmp";
saveBmp(writePath, imagedataScal, FloatToIntwidth, FloatToIntheight, biBitCount, pColorTable);
printf("缩放变换完成,请查看Scalresult.bmp文件。\n\n");
//释放内存
delete[] imagedata;
delete[] imagedataScal;
}