本博文内容是博文基于MFC框架的图像缩放算法示例的一部分(返回目录)。
本博文介绍双三次插值(Bicubic Interpolation)缩放算法的实现。
**双三次插值算法(Bicubic Interpolation)**原理
**双三次插值算法(Bicubic Interpolation)**原理如下图所示。
双三次插值是二维空间中常用的插值方法。在这种方法中,目标点P的像素值可以通过源图像中最近的十六个采样点的加权平均得到。
用公式可以表达为:
其中p是待求的目标像素值,aij是原图像邻近像素点的权值,pij是原图像邻近像素点的像素值。
基于对权值aij的不同取值方法,双三次插值算法又可以分为多个不同子算法。常见的有基于三角采样函数、Bell分布采样函数和B样条采样函数。
我们这里使用B样条采样函数实现。
-
B样条插值函数原理如下图所示。
-
B样条插值核函数:
-
总结一下,双三次插值用矩阵乘法可以表示为如下公式
step 01: 为工程添加一个名为CScaleBicubic的类
依次点击菜单项【项目】->【添加类】,会出现添加新的界面,填好类名CScaleBicubic
和文件名Scale_Bicubic.h
和Scale_Bicubic.cpp
点击【确定】按钮即可。
将Scale_Bicubic.h
修改内容如下:
#pragma once
class CScaleBicubic
{
public:
CScaleBicubic();
~CScaleBicubic();
public:
float pix_weight(float x);
int src_pix_pos(int in_width, int out_width, int dst_pos, int* src_pos, float* src_weight);
int line_scale(unsigned char* in_line, int in_width, unsigned char* out_line, int out_width);
int frame_scale(unsigned char* imgIn, int in_width, int in_height, unsigned char* imgOut, int out_width, int out_height);
};
将Scale_Bicubic.cpp
修改内容如下:
#include "pch.h"
#include "scale_bicubic.h"
CScaleBicubic::CScaleBicubic() {}
CScaleBicubic::~CScaleBicubic() {}
//计算源图像中目标像素的四个近邻像素的对应权值
float CScaleBicubic::pix_weight(float x) {
if (x < 0) x = -x; //x=abs(x);
if (x >= 2) return 0;
float s = 0;
if (x < 1) s = 1 - 2 * x * x + x * x * x;
else s = 4 - 8 * x + 5 * x * x - x * x * x;
return s;
}
//给定目标像素位置,计算源图像中目标像素的四个近邻像素的坐标及其权值
int CScaleBicubic::src_pix_pos(int in_width, int out_width, int dst_pos, int* src_pos, float* src_weight)
{
float scale = (float)(in_width) / (float)out_width;
float srcpos_f, src_re;
int srcpos_i;
//源图像对应的近邻坐标
srcpos_f = dst_pos * scale;
srcpos_i = (int)srcpos_f;
src_re = srcpos_f - srcpos_i;
src_pos[0] = srcpos_i - 1;
src_pos[1] = srcpos_i;
src_pos[2] = srcpos_i + 1;
src_pos[3] = srcpos_i + 2;
//对边界坐标进行处理
for (int i = 0; i < 4; i++) {
if (src_pos[i] < 0) src_pos[i] = 0;
if (src_pos[i] > (in_width - 1)) src_pos[i] = in_width - 1;
}
//计算四个相邻坐标的对应权值
src_weight[0] = pix_weight(1 + src_re);
src_weight[1] = pix_weight(src_re);
src_weight[2] = pix_weight(1 - src_re);
src_weight[3] = pix_weight(2 - src_re);
return 0;
}
//用两轮一维双三次插值实现二维双三次插值算法
//此函数实现一维双三次插值
int CScaleBicubic::line_scale(unsigned char* in_line, int in_width, unsigned char* out_line, int out_width) {
int src_pos[4];
float src_weight[4];
float tmp = 0;
for (int i = 0; i < out_width; i++)
{
//计算目标像素在源图像一行或一列的四个相邻像素及其权值
src_pix_pos(in_width, out_width, i, src_pos, src_weight);
tmp = 0;
for (int k = 0; k < 4; k++)
tmp += src_weight[k] * (float)in_line[src_pos[k]];
if (tmp > 255)
out_line[i] = 255;
else if (tmp < 1)
out_line[i] = 1;
else
out_line[i] = (unsigned char)tmp;
}
return 0;
}
int CScaleBicubic::frame_scale(unsigned char* imgIn, int in_width, int in_height, unsigned char* imgOut, int out_width, int out_height) {
unsigned char* tmp_frame = new unsigned char[out_width * in_height * sizeof(unsigned char)];
unsigned char* tmp_srcline = 0;
unsigned char* tmp_dstline = 0;
int i = 0, j = 0, k = 0;
int srcline_index = 0;
int tmp = 0;
int src_pos[4];
float src_weight[4];
int pix[4];
int pix_pos[4];
//stage 1: scale each colomn
for (i = 0; i < in_height; i++) {
tmp_srcline = imgIn + i * in_width;
tmp_dstline = tmp_frame + i * out_width;
line_scale(tmp_srcline, in_width, tmp_dstline, out_width);
}
//stage 2: scale each row
for (i = 0; i < out_height; i++) {
//计算目标行在源图像的四个相邻行及其对应权值
src_pix_pos(in_height, out_height, i, src_pos, src_weight);
for (j = 0; j < out_width; j++) {
tmp = 0;
for (k = 0; k < 4; k++) {
tmp_dstline = tmp_frame + src_pos[k] * out_width;
//pix[k] = src_pos[k] * out_width + j;
tmp += src_weight[k] * tmp_dstline[j];
}
if (tmp > 255)
imgOut[i * out_width + j] = 255;
else if (tmp < 1)
imgOut[i * out_width + j] = 1;
else
imgOut[i * out_width + j] = (unsigned char)tmp;
}
}
delete[] tmp_frame;
return 0;
}
step 02: 在鼠标左键和右键点击消息并进行图像缩放
前一篇博文07. 基于MFC实现双线性插值图像缩放算法我们已经实现了鼠标点击左键时绘制BMP原图,点击右键显示双线性插值缩放图的比对效果。
新的双三次缩放算法对外的函数接口与前两个算法的相比区别不大,在CView类中使用时只需更新类名并稍作修改即可,在此不再赘述,请同学们自行补上,观察效果。
为了观察三种缩放算法的差别,我们需要对一幅特别精细的图像进行缩放才有明显效果,如下图所示,最左边是原图,才有Windows绘图工具绘制的单像素细线条图像,向右依次是使用最近邻、双线性插值和双三次样条曲线插值算法缩放的结果,缩放比例相同。可以看出,缩放后效果越来越好。
- 最近邻、双线性插值和双三次样条曲线插值缩放算法的效果比较图
扩展练习
- 大家应该注意到,在点击鼠标进行绘制操作的时候我们需要比较多的步骤完成,基于GUI界面代码和核心代码尽可能低耦合的原则,我们可以对前述读取BMP、分离RGB通道,分别缩放RGB三个通道,最后组合三个通道进行绘制的过程进行进行整合。
- 给个提示,新建一个
CBmpScale
类,将上述过程整合,并在CView内调用,用非常简明的代码完成上图的绘制。