本博文内容是博文基于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)
-
方法:双线性插值分两步实现
- 通过c00,c10线性插值得到a,通过c01,c11线性插值得到b;
- 通过a,b线性插值得到c.
- 实现:类似最近邻缩放算法,双线性插值算法也是先进行水平方向的行插值,再进行垂直方向上的列插值,也可以先列插值再行插值,插值方向的顺序不影响最终的结果。
step 01: 为工程添加一个名为CScaleBilinear的类
在名为MFC_SD_03的工程依次点击菜单项【项目】->【添加类】,会出现添加新的界面,填好类名CScaleBilinear
和文件名Scale_Bilinear.h
和Scale_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两个类,我们就可以分步实现图像缩放了。即
- 读取图像
CBmp::readBmp()
- 分离通道
CBmp::separateRGB()
- 缩放每个通道
CScaleBilinear::frame_scale_bilinear
- 合并绘制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图像。