2. 图像缩放与裁剪

任务目标

实现一个支持双线性插值缩放与中心裁剪的图像尺寸调整模块,输入任意尺寸的RGB图像,输出指定目标尺寸的图像。缩放与裁剪可独立调用,也可组合使用。

核心实现

// image_resize.h
#pragma once
#include <cstdint>
#include <vector>

struct Image {
    int width, height, channels;
    std::vector<uint8_t> data;  // 按行优先存储,RGB/RGBA交错
};

// 双线性插值缩放
Image resize_bilinear(const Image& src, int dst_w, int dst_h);

// 中心裁剪
Image center_crop(const Image& src, int crop_w, int crop_h);
// image_resize.cpp
#include "image_resize.h"
#include <algorithm>
#include <cmath>

static uint8_t clamp_u8(float v) {
    return static_cast<uint8_t>(std::max(0.0f, std::min(255.0f, v)));
}

Image resize_bilinear(const Image& src, int dst_w, int dst_h) {
    Image dst;
    dst.width = dst_w;
    dst.height = dst_h;
    dst.channels = src.channels;
    dst.data.resize(dst_w * dst_h * dst.channels);

    float scale_x = static_cast<float>(src.width) / dst_w;
    float scale_y = static_cast<float>(src.height) / dst_h;

    for (int y = 0; y < dst_h; ++y) {
        float src_y = (y + 0.5f) * scale_y - 0.5f;
        src_y = std::max(0.0f, src_y);
        int y0 = static_cast<int>(src_y);
        int y1 = std::min(y0 + 1, src.height - 1);
        float fy = src_y - y0;

        for (int x = 0; x < dst_w; ++x) {
            float src_x = (x + 0.5f) * scale_x - 0.5f;
            src_x = std::max(0.0f, src_x);
            int x0 = static_cast<int>(src_x);
            int x1 = std::min(x0 + 1, src.width - 1);
            float fx = src_x - x0;

            for (int c = 0; c < src.channels; ++c) {
                float top = src.data[(y0 * src.width + x0) * src.channels + c] * (1 - fx) +
                            src.data[(y0 * src.width + x1) * src.channels + c] * fx;
                float bottom = src.data[(y1 * src.width + x0) * src.channels + c] * (1 - fx) +
                               src.data[(y1 * src.width + x1) * src.channels + c] * fx;
                float val = top * (1 - fy) + bottom * fy;
                dst.data[(y * dst_w + x) * dst.channels + c] = clamp_u8(val);
            }
        }
    }
    return dst;
}

Image center_crop(const Image& src, int crop_w, int crop_h) {
    crop_w = std::min(crop_w, src.width);
    crop_h = std::min(crop_h, src.height);

    int start_x = (src.width - crop_w) / 2;
    int start_y = (src.height - crop_h) / 2;

    Image dst;
    dst.width = crop_w;
    dst.height = crop_h;
    dst.channels = src.channels;
    dst.data.resize(crop_w * crop_h * src.channels);

    for (int y = 0; y < crop_h; ++y) {
        const uint8_t* src_row = &src.data[((start_y + y) * src.width + start_x) * src.channels];
        uint8_t* dst_row = &dst.data[y * crop_w * src.channels];
        std::copy(src_row, src_row + crop_w * src.channels, dst_row);
    }
    return dst;
}

运行与验证

// main.cpp —— 最小验证入口
#include "image_resize.h"
#include <cstdio>

int main() {
    // 构造 4x4 灰度测试图(channels=1),像素值 = 行号*10 + 列号
    Image src;
    src.width = 4; src.height = 4; src.channels = 1;
    for (int y = 0; y < 4; ++y)
        for (int x = 0; x < 4; ++x)
            src.data.push_back(static_cast<uint8_t>(y * 10 + x));

    // 测试1:缩放 4x4 → 2x2
    Image scaled = resize_bilinear(src, 2, 2);
    printf("缩放结果 2x2:\n");
    for (int y = 0; y < 2; ++y) {
        for (int x = 0; x < 2; ++x)
            printf("%3d ", scaled.data[y * 2 + x]);
        printf("\n");
    }

    // 测试2:中心裁剪 4x4 → 2x2
    Image cropped = center_crop(src, 2, 2);
    printf("裁剪结果 2x2:\n");
    for (int y = 0; y < 2; ++y) {
        for (int x = 0; x < 2; ++x)
            printf("%3d ", cropped.data[y * 2 + x]);
        printf("\n");
    }
    return 0;
}

编译运行:

g++ -O2 -o resize_test main.cpp image_resize.cpp
./resize_test

预期输出与判定标准

由于输入为确定性构造数据,可精确计算预期结果:

  • 缩放 4x4→2x2:双线性插值采样点位于原图 (0.75,0.75)、(2.25,0.75)、(0.75,2.25)、(2.25,2.25) 附近,预期输出约为:
    3 5
    13 15(四舍五入后)
  • 中心裁剪 4x4→2x2:取原图中心区域,即行1~2、列1~2,预期输出:
    11 12
    21 22

通过条件:程序输出与上述预期一致(允许缩放结果因浮点舍入有±1偏差)。若需验证彩色图像,将 channels 改为 3 并填充 RGB 数据即可,算法逻辑不变。

原图 4x4 0123 10111213 20212223 30313233 缩放 缩放 2x2 35 1315 中心裁剪 裁剪 2x2 1112 2122 裁剪区域(虚线框)

说明:本实现为纯CPU端C++代码,不依赖第三方库,可直接编译运行。若需在嵌入式GPU上部署,可将核心循环替换为OpenCL/OpenGL着色器实现,但插值与裁剪的坐标映射逻辑完全一致。