快速 2:1 图像缩小(缩放)






4.93/5 (23投票s)
快速实现 2:1 图像缩小(
引言
本文提供了一种简单快速、实用的 2:1 图像缩小算法实现。
2:1 图像缩小用于非常具体的情况,但效果很好。
背景
图像处理总是一项昂贵的工作。当需要显示缩放后的图片时,最好使用 GPU 进行渲染(OpenGL、DirectX)。但使用 GPU 并非总是可能,因此拥有一个替代方案是好的。
高质量的图像调整大小(放大/缩小)对 CPU 的要求极高,尤其是在大型图像上。此外,有时需要多次进行处理。为了缓解这种情况,可以应用不同的技巧。如果需要缩小(缩放)图片,可以应用类似于 mip-map 的技巧。例如,如果我们想将图片缩小两倍以上(目标尺寸小于原始尺寸的一半),我们可以通过在实际缩放之前应用半缩小来加速整个过程。2:1 缩放非常快,因为它使用最简单的算法,计算量较小。
例如,如果我们有 1920x1024 的图像,需要将其缩小到 500x281,我们需要执行这样的代码
shrink( imageDst, imageSrc )
但是我们可以通过预先进行 2:1 缩小来优化这一点,如下所示
shrinkHalf( imageHalf, imageSrc );
shrink( imaheDst, imageHalf );
在我的一个项目中,当我必须缩放 3 个显示器的图像以生成预览时,结果是
Scale: 46 ms
ShrinkHalf + Scale: 25 ms
ShrinkHalf + ShrinkHalf + Scale: 16 ms
这意味着,应用一次技巧,会提高 36% 的速度。
而应用两次技巧,会提高 66% 的速度。
演示应用程序有两个按钮
- 缩小 2:1 - 对整个源图像执行图像缩小
- 缩小 2:1 rnd - 对源图像中的随机矩形执行图像缩小
左边是原始图像。右边是灰度显示的原始图像和彩色显示的缩小图像(或缩小部分)。
仅缩小图像的一部分适用于原始图像已更新并且需要更新缩小副本的情况。这可以节省大量的 CPU 和处理时间。
对整个图片的 2:1 缩小
对随机矩形的 2:1 缩小
Using the Code
项目中有一对文件:ShrinkHalf.h 和 ShrinkHalf.cpp
要使用代码,您只需要将这些文件放入您的项目中并包含 ShrinkHalf.h 文件。
这些文件包含了所有算法的实现。
shinkHalf 函数 具有以下 参数
BYTE* dstPixels
- 目标 像素数组,格式为 24 位 BGR (b,g,r, b,g,r, ..., b,g,r)BYTE* srcPixels
- 源 像素数组,格式为 24 位 BGR (b,g,r, b,g,r, ..., b,g,r)int srcWidth
- 图像的像素宽度int srcHeight
- 图像的像素高度
因此,为了使用这些函数,您应该这样做
#include "ShrinkHalf.h"
...
// This will perform 2:1 scale-down with color averaging
shrinkHalf( dstPixels, srcPixels, srcWidth, srcHeight );
请注意,目标不提供尺寸,因为它们是从源尺寸计算出来的。
以下是可用的函数
/////////////////////////////////////////////////////////////////////////////
// Fast and smooth 2:1 image resize
/////////////////////////////////////////////////////////////////////////////
void shrinkHalf ( BYTE* target, const BYTE* source, int srcWidth, int srcHeight );
void shrinkHalfPart( BYTE* target, const BYTE* source, int srcWidth,
int srcHeight, int x1, int y1, int x2, int y2 );
/////////////////////////////////////////////////////////////////////////////
第一个函数对整个图像执行 2:1 缩小。
第二个函数对源图像中指定的矩形执行 2:1 缩小,并为目标图像计算相应的矩形。
如果您使用它,您应该注意您可能需要对源矩形点进行 +1/-1 的更正,因为存在整数四舍五入问题。
这对于需要进行大量更新的情况(例如,视频渲染)非常有用。
关注点
在 shrinkHalf
函数中,有一个注释掉的代码可以工作,但我更喜欢保留对 shrinkHalfPart
的内部调用。
//static
void shrinkHalf( BYTE* target, const BYTE* source, int srcWidth, int srcHeight )
{
shrinkHalfPart( target, source, srcWidth,
srcHeight, 0, 0, srcWidth-1, srcHeight-1 );
//~~~~~
/*
int dstWidth = srcWidth / 2;
int dstHeight = srcHeight / 2;
int srcLineBytes = srcWidth * 3;
int dstLineBytes = dstWidth * 3;
int dstRows = dstHeight;
const BYTE* sl = source;
BYTE* tl = target;
const BYTE* te = target + srcHeight/2 * dstLineBytes;
while( tl < te )
{
BYTE* pt = tl;
const BYTE* p1 = sl;
const BYTE* p2 = p1 + srcLineBytes;
const BYTE* pe = sl + srcLineBytes;
while( p1 < pe )
{
*pt++ = (p1[0] + p1[3] + p2[0] + p2[3]) >> 2; // / 4; blue;
*pt++ = (p1[1] + p1[4] + p2[1] + p2[4]) >> 2; // / 4; green;
*pt++ = (p1[2] + p1[5] + p2[2] + p2[5]) >> 2; // / 4; red;
p1 += 6;//2*3;
p2 += 6;//2*3;
}
sl += srcLineBytes << 1; // Shift by 1 is equal to " * 2 "
tl += dstLineBytes;
}
*/
}
因此,如果有人想要一个干净且更简单的函数,那么可以移除调用 shrinkHalfPart
的第一行并取消注释注释掉的代码。
历史
- 2020 年 4 月 8 日:初始版本