09. 基于MFC实现双三次插值图像缩放算法

本博文内容是博文基于MFC框架的图像缩放算法示例的一部分(返回目录)。

本博文介绍双三次插值(Bicubic Interpolation)缩放算法的实现。

**双三次插值算法(Bicubic Interpolation)**原理

**双三次插值算法(Bicubic Interpolation)**原理如下图所示。
在这里插入图片描述

双三次插值是二维空间中常用的插值方法。在这种方法中,目标点P的像素值可以通过源图像中最近的十六个采样点的加权平均得到。

用公式可以表达为:
在这里插入图片描述

其中p是待求的目标像素值,aij是原图像邻近像素点的权值,pij是原图像邻近像素点的像素值。

基于对权值aij的不同取值方法,双三次插值算法又可以分为多个不同子算法。常见的有基于三角采样函数、Bell分布采样函数和B样条采样函数。

我们这里使用B样条采样函数实现。

  • B样条插值函数原理如下图所示。
    在这里插入图片描述

  • B样条插值核函数:
    在这里插入图片描述

  • 总结一下,双三次插值用矩阵乘法可以表示为如下公式
    在这里插入图片描述

step 01: 为工程添加一个名为CScaleBicubic的类

依次点击菜单项【项目】->【添加类】,会出现添加新的界面,填好类名CScaleBicubic和文件名Scale_Bicubic.hScale_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内调用,用非常简明的代码完成上图的绘制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值