07. 基于MFC实现双线性插值图像缩放算法

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

博文05. 基于MFC实现最近邻图像缩放算法实现了最简单的最近邻算法,本文在此基础上添加双线性插值缩放算法的实现。

*双线性插值算法(Bilinear Interpolation)*原理如下图所示。所谓双线性插值,即在两个方向分别进行一次线性插值,通过四个相邻像素插值得到待求像素,距离越近的像素贡献越大。
在这里插入图片描述

  • 公式:已知c00,c10,c01,c11为原图中的四邻像素,c点为待求像素。

    f© = f(c00)(1-tx)(1-ty)+f(c10)(tx)(1-ty)+f(c11)(tx)(ty)+f(c01)(tx)(ty)

  • 方法:双线性插值分两步实现

  1. 通过c00,c10线性插值得到a,通过c01,c11线性插值得到b;
  2. 通过a,b线性插值得到c.
  • 实现:类似最近邻缩放算法,双线性插值算法也是先进行水平方向的行插值,再进行垂直方向上的列插值,也可以先列插值再行插值,插值方向的顺序不影响最终的结果。

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

在名为MFC_SD_03的工程依次点击菜单项【项目】->【添加类】,会出现添加新的界面,填好类名CScaleBilinear和文件名Scale_Bilinear.hScale_Bilinear.cpp点击【确定】按钮即可。

Scale_Bilinear.h修改内容如下:

#pragma once
class CScaleBilinear
{
public:
	CScaleBilinear();
	~CScaleBilinear();
public:
	float pix_weight_bilinear(float x);
	int src_pix_pos_bilinear(int in_width, int out_width, int dst_pos, 
                             int* src_pos, float* src_weight);
	int line_scale_bilinear(unsigned char* in_line, int in_width, 
                            unsigned char* out_line, int out_width);
    int col_scale_bilinear(unsigned char* in_line, int in_height, 
							unsigned char* out_line, int out_height, int out_width);
	int frame_scale_bilinear(unsigned char* imgIn, int in_width, int in_height, 
                             unsigned char* imgOut, int out_width, int out_height);
};

scale_bilinear.cpp修改内容如下:

#include "pch.h"

#include "scale_bilinear.h"
 
CScaleBilinear::CScaleBilinear() {}

CScaleBilinear::~CScaleBilinear() {}

//计算源图像中目标像素的四个近邻像素的对应权值
float CScaleBilinear::pix_weight_bilinear(float x)
{
	if (x < 0) x = -x; //x=abs(x);	
	return 1-x;
}

//给定目标像素位置,计算源图像中目标像素的四个近邻像素的坐标及其权值
int CScaleBilinear::src_pix_pos_bilinear(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;

	//对边界坐标进行处理
	for (int i = 0; i < 2; 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_bilinear(1 - src_re);
	src_weight[1] = pix_weight_bilinear(src_re);

	return 0;
}

//用两轮一维双三次插值实现二维双三次插值算法
//此函数实现一维双三次插值
int CScaleBilinear::line_scale_bilinear(unsigned char* in_line, int in_width, unsigned char* out_line, int out_width) {
	int src_pos[2];
	float src_weight[2];
	float tmp = 0;
	for (int i = 0; i < out_width; i++)
	{
		//计算目标像素在源图像一行或一列的四个相邻像素及其权值
		src_pix_pos_bilinear(in_width, out_width, i, src_pos, src_weight);
		tmp = 0;
		for(int k=0;k<2;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 CScaleBilinear::col_scale_bilinear(unsigned char* in_line, int in_height,
	unsigned char* out_line, int out_height, int out_width)
{
	int src_pos[2];
	float src_weight[2];
	float tmp = 0;
	for (int i = 0; i < out_height; i++)
	{
		// 计算目标像素在源图像中一列的四个相邻像素及其权值
		src_pix_pos_bilinear(in_height, out_height, i, src_pos, src_weight);
		tmp = 0;
		for (int k = 0; k < 2; k++)
		{
			tmp += src_weight[k] * (float)in_line[src_pos[k] * out_width];
		}

		if (tmp > 255)
			out_line[i * out_width] = 255;
		else if (tmp < 1)
			out_line[i * out_width] = 1;
		else
			out_line[i * out_width] = (unsigned char)tmp;
	}

	return 0;
}
 
int CScaleBilinear::frame_scale_bilinear(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;
	unsigned char* tmp_srcCol = 0;
	unsigned char* tmp_dstCol = 0;

	int i = 0, j = 0, k = 0;
	int srcline_index = 0;
	int tmp = 0;
	//int src_pos[2];
	//float src_weight[2];

	//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_bilinear(tmp_srcline, in_width, tmp_dstline, out_width);
	}

	//stage 2: scale each row
	for (i = 0; i < out_width; i++)
	{
		tmp_srcCol = tmp_frame + i;
		tmp_dstCol = imgOut + i;

		col_scale_bilinear(tmp_srcCol, in_height, tmp_dstCol, out_height, out_width);
	}

	delete[] tmp_frame;

	return 0;
}

step 02: 在鼠标左键消息并进行图像缩放

前一篇博文我们已经实现了鼠标点击左键时绘制BMP原图,为了比对缩放图像和原图,这次我们显示双线性插值缩放算法的结果,并与最近邻算法结果进行比较。

有了CBmp和CScaleBilinear两个类,我们就可以分步实现图像缩放了。即

  1. 读取图像CBmp::readBmp()
  2. 分离通道CBmp::separateRGB()
  3. 缩放每个通道CScaleBilinear::frame_scale_bilinear
  4. 合并绘制BMP图像CBmp::print_matrix

可以看出,与最近邻算法相比,应用双线性插值算法缩放图像的步骤基本一致,只在具体缩放时有所区别。为显示区别,我们特意将双线性缩放比例int dstWidth = bmpWidth / 1.5;设为与最近邻算法不同的比例int dstWidth = bmpWidth / 2;

#include "scale_bilinear.h"
void CMFCSD03View::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	CBmp bmp;
	CScaleBilinear scaler;

	//要绘制的图像文件名
	char bmpName[] = "1.bmp";
	unsigned char* red_channel = new unsigned char[1920 * 1080];
	unsigned char* green_channel = new unsigned char[1920 * 1080];
	unsigned char* blue_channel = new unsigned char[1920 * 1080];

	unsigned char* img_data = new unsigned char[1920 * 1080 * 3];

	int bmpWidth = 0;
	int bmpHeight = 0;
	int biBitCount = 0;
	int lineByte = 0;

	//read bmp image
	bmp.readBmp(bmpName, img_data, &bmpWidth, &bmpHeight, &biBitCount, &lineByte);

	//separate to three channels
	bmp.separateRGB(img_data,
		red_channel, green_channel, blue_channel,
		bmpWidth, bmpHeight, lineByte);
	
	//scale the image
	int dstWidth = bmpWidth / 1.5;
	int dstHeight = bmpHeight / 2;

	scaler.frame_scale_bilinear(
		red_channel, bmpWidth, bmpHeight,
		red_channel, dstWidth, dstHeight);

	scaler.frame_scale_bilinear(
		green_channel, bmpWidth, bmpHeight,
		green_channel, dstWidth, dstHeight);

	scaler.frame_scale_bilinear(
		blue_channel, bmpWidth, bmpHeight,
		blue_channel, dstWidth, dstHeight);


	//合并绘制三个通道的图像
	CClientDC dc(this);
	CDC* pDC = &dc;

	int offset_left = point.x;
	int offset_top = point.y;

	bmp.print_matrix(pDC,
		red_channel, green_channel, blue_channel,
		dstWidth, dstHeight,
		offset_left, offset_top);

	//important to clear the memory used
	delete[] red_channel;
	delete[] green_channel;
	delete[] blue_channel;
	delete[] img_data;

	CView::OnLButtonDown(nFlags, point);
}

step 02: 编译执行MFC工程

编译执行MFC工程,会发现每次点击鼠标左键都会以点击的位置为左上角绘制指定的BMP图像双线性插值算法缩放后的图像,而点击右键时会绘制应用最近邻算法缩放后的BMP图像。
在这里插入图片描述

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值