金字塔降低采样

文章目录

image_scale.hpp

cpp 复制代码
#ifndef IMAGE_SCALE_HPP
#define IMAGE_SCALE_HPP

#include <vector>
#include <cstdint>
#include <utility> // for std::pair
#include <algorithm> 
#include <string>
enum class ScaleMethod {
    Nearest,    // 最近邻插值
    Bilinear,   // 双线性插值
    Bicubic,    // 双三次插值
    Pyramid     // 金字塔降采样
};

struct Image {
    std::vector<uint8_t> data;
    int width = 0;
    int height = 0;
    int channels = 0;
    float dpi = 0.0f;

    Image(int w, int h, int c, float d = 0.0f)
        : width(w), height(h), channels(c), dpi(d), data(w* h* c) {
    }

    // 安全获取像素(带边界检查)
    uint8_t get_pixel(int x, int y, int c) const {
        x = std::clamp(x, 0, width - 1);
        y = std::clamp(y, 0, height - 1);
        return data[(y * width + x) * channels + c];
    }
};

Image scale_image(const Image& src,
    std::pair<int, int> dst_size,
    float target_dpi  ,
    ScaleMethod method  );

/**
 * 从 JPEG 文件读取图像数据(使用 TurboJPEG)
 * @param path      JPEG 文件路径
 * @param dst_dpi   目标DPI(若<=0 则使用文件默认DPI)
 * @return          Image 结构(数据自动管理)
 * @throws std::runtime_error 读取失败时抛出
 */
Image read_jpeg(const std::string& path);



/**
 * 将 Image 编码为 JPEG 字节流
 * @param img       输入图像(支持 RGB/RGBA)
 * @param quality   压缩质量(1-100)
 
 * @return          JPEG 二进制数据
 */
std::vector<uint8_t> encode_jpeg(const Image& img, int quality  );

/**
 * 将 JPEG 数据保存到文件
 * @param path      输出路径
 * @param img       输入图像
 * @param quality   压缩质量(1-100)
 */
void save_jpeg(const std::string& path, const Image& img, int quality );

#endif // IMAGE_SCALE_HPP

image_scale.cpp

cpp 复制代码
#include "image_scale.hpp"
#include <cmath>
#include <algorithm>
#include <stdexcept>

namespace {
    // 双三次插值核
    float bicubic_kernel(float x, float B = 0.0f, float C = 0.5f) {
        x = std::abs(x);
        if (x < 1.0f) {
            return ((12 - 9 * B - 6 * C) * x * x * x + (-18 + 12 * B + 6 * C) * x * x + (6 - 2 * B)) / 6.0f;
        }
        else if (x < 2.0f) {
            return ((-B - 6 * C) * x * x * x + (6 * B + 30 * C) * x * x + (-12 * B - 48 * C) * x + (8 * B + 24 * C)) / 6.0f;
        }
        return 0.0f;
    }

    // 单次降采样(双线性)
    Image downscale_half(const Image& src) {
        if (src.width <= 1 || src.height <= 1)
            throw std::invalid_argument("Image too small for downscaling");

        Image dst(src.width / 2, src.height / 2, src.channels, src.dpi / 2.0f);

        for (int y = 0; y < dst.height; ++y) {
            for (int x = 0; x < dst.width; ++x) {
                for (int c = 0; c < src.channels; ++c) {
                    // 2x2 区域均值
                    float p = (
                        src.get_pixel(x * 2, y * 2, c) +
                        src.get_pixel(x * 2 + 1, y * 2, c) +
                        src.get_pixel(x * 2, y * 2 + 1, c) +
                        src.get_pixel(x * 2 + 1, y * 2 + 1, c)
                        ) / 4.0f;
                    dst.data[(y * dst.width + x) * src.channels + c] = static_cast<uint8_t>(p);
                }
            }
        }
        return dst;
    }

    // 计算基于 DPI 的目标尺寸
    std::pair<int, int> calculate_target_size(const Image& src, float target_dpi) {
        if (target_dpi <= 0 || src.dpi <= 0)
            return { src.width, src.height }; // 忽略 DPI 计算

        float scale = target_dpi / src.dpi;
        return {
            static_cast<int>(std::round(src.width * scale)),
            static_cast<int>(std::round(src.height * scale))
        };
    }
}

Image scale_image(const Image& src,
    std::pair<int, int> dst_size,
    float target_dpi,
    ScaleMethod method)
{
    auto [dst_width, dst_height] = dst_size;

    // 1. 根据 DPI 调整目标尺寸
    if (target_dpi > 0) {
        auto dpi_size = calculate_target_size(src, target_dpi);
        dst_width = dpi_size.first;
        dst_height = dpi_size.second;
    }

    // 2. 金字塔降采样
    if (method == ScaleMethod::Pyramid &&
        (dst_width < src.width || dst_height < src.height))
    {
        Image current = src;

        // 逐级减半直到接近目标尺寸
        while (current.width / 2 >= dst_width &&
            current.height / 2 >= dst_height) {
            current = downscale_half(current);
        }

        // 最终精确缩放
        if (current.width != dst_width || current.height != dst_height) {
            return scale_image(current, { dst_width, dst_height }, -1.0f, ScaleMethod::Bilinear);
        }
        return current;
    }

    // 3. 常规缩放
    Image dst(dst_width, dst_height, src.channels,
        (target_dpi > 0) ? target_dpi : src.dpi * (static_cast<float>(dst_width) / src.width));

    const float x_ratio = static_cast<float>(src.width - 1) / dst_width;
    const float y_ratio = static_cast<float>(src.height - 1) / dst_height;

    for (int y = 0; y < dst_height; ++y) {
        for (int x = 0; x < dst_width; ++x) {
            const float src_x = x * x_ratio;
            const float src_y = y * y_ratio;

            for (int c = 0; c < src.channels; ++c) {
                float pixel = 0.0f;

                switch (method) {
                case ScaleMethod::Nearest: {
                    int nx = static_cast<int>(src_x + 0.5f);
                    int ny = static_cast<int>(src_y + 0.5f);
                    pixel = src.get_pixel(nx, ny, c);
                    break;
                }

                case ScaleMethod::Bilinear: {
                    int x0 = static_cast<int>(src_x);
                    int y0 = static_cast<int>(src_y);
                    float dx = src_x - x0;
                    float dy = src_y - y0;

                    pixel =
                        src.get_pixel(x0, y0, c) * (1 - dx) * (1 - dy) +
                        src.get_pixel(x0 + 1, y0, c) * dx * (1 - dy) +
                        src.get_pixel(x0, y0 + 1, c) * (1 - dx) * dy +
                        src.get_pixel(x0 + 1, y0 + 1, c) * dx * dy;
                    break;
                }

                case ScaleMethod::Bicubic: {
                    int x0 = static_cast<int>(src_x) - 1;
                    int y0 = static_cast<int>(src_y) - 1;
                    float sum = 0.0f, weight_sum = 0.0f;

                    for (int i = 0; i < 4; ++i) {
                        for (int j = 0; j < 4; ++j) {
                            float wx = bicubic_kernel(src_x - (x0 + i));
                            float wy = bicubic_kernel(src_y - (y0 + j));
                            float w = wx * wy;

                            sum += src.get_pixel(x0 + i, y0 + j, c) * w;
                            weight_sum += w;
                        }
                    }
                    pixel = sum / (weight_sum + 1e-8f);
                    break;
                }

                default:
                    throw std::invalid_argument("Unsupported scale method");
                }

                dst.data[(y * dst.width + x) * src.channels + c] =
                    static_cast<uint8_t>(std::clamp(pixel, 0.0f, 255.0f));
            }
        }
    }

    return dst;
}


//#include "jpeg_reader.hpp"
#include <turbojpeg.h>
#include <fstream>
#include <vector>
#include <memory> // std::unique_ptr

// 自动释放 TurboJPEG 实例的 RAII 包装器
struct TJDeleter {
    void operator()(tjhandle h) const { if (h) tjDestroy(h); }
};
using TJHandle = std::unique_ptr<void, TJDeleter>;

Image read_jpeg(const std::string& path) {
    // 1. 读取文件到内存
    std::ifstream file(path, std::ios::binary | std::ios::ate);
    if (!file) throw std::runtime_error("Cannot open file: " + path);

    const size_t file_size = file.tellg();
    file.seekg(0);
    std::vector<uint8_t> jpeg_data(file_size);
    if (!file.read(reinterpret_cast<char*>(jpeg_data.data()), file_size)) {
        throw std::runtime_error("Failed to read file: " + path);
    }

    // 2. 初始化 TurboJPEG
    TJHandle jpeg(tjInitDecompress());
    if (!jpeg) throw std::runtime_error("TurboJPEG init failed: " + std::string(tjGetErrorStr()));

    // 3. 获取图像信息(使用 tjDecompressHeader3)
    int width, height, subsamp, colorspace;
    if (tjDecompressHeader3(
        jpeg.get(), jpeg_data.data(), jpeg_data.size(),
        &width, &height, &subsamp, &colorspace) != 0
        ) {
        throw std::runtime_error("JPEG header error: " + std::string(tjGetErrorStr()));
    }

    

    // 5. 分配输出缓冲区(RGB 格式)
    const int pixel_format = TJPF_RGB; // 输出格式
    const int pixel_size = tjPixelSize[pixel_format];
    Image img(width, height, pixel_size);

    // 6. 解压图像(使用 tjDecompress3)
    if (tjDecompress2(
        jpeg.get(),
        jpeg_data.data(), jpeg_data.size(),
        img.data.data(), width, 0, height,
        pixel_format,
        TJFLAG_FASTDCT | TJFLAG_NOREALLOC // 禁止内部重分配
      ) != 0          // 忽略 ROI 和元数据
        ) {
        throw std::runtime_error("JPEG decompress failed: " + std::string(tjGetErrorStr()));
    }

    return img;
}


// 编码实现
std::vector<uint8_t> encode_jpeg(const Image& img, int quality ) {
    // 参数校验
    if (img.data.empty() || img.width <= 0 || img.height <= 0) {
        throw std::runtime_error("Invalid image data");
    }
    if (quality < 1 || quality > 100) {
        throw std::runtime_error("Quality must be between 1-100");
    }

    // 初始化 TurboJPEG 压缩器
    TJHandle jpeg(tjInitCompress());
    if (!jpeg) {
        throw std::runtime_error("TurboJPEG init failed: " + std::string(tjGetErrorStr()));
    }

    // 设置像素格式
    int pixel_format;
    switch (img.channels) {
    case 1: pixel_format = TJPF_GRAY; break;
    case 3: pixel_format = TJPF_RGB;   break;
    case 4: pixel_format = TJPF_RGBA;  break;
    default:
        throw std::runtime_error("Unsupported image channels");
    }

    // 压缩 JPEG
    uint8_t* jpeg_buf = nullptr;
    unsigned long jpeg_size = 0;

    if (tjCompress2(
        jpeg.get(),
        img.data.data(), img.width, 0, img.height,
        pixel_format,
        &jpeg_buf, &jpeg_size,
        TJSAMP_444, // 4:4:4 色度采样(最高质量)
        quality,
        TJFLAG_ACCURATEDCT // 高精度 DCT
    ) != 0) {
        throw std::runtime_error("JPEG compression failed: " + std::string(tjGetErrorStr()));
    }

    // 复制数据到 vector(TurboJPEG 需要手动释放内存)
    std::vector<uint8_t> result(jpeg_buf, jpeg_buf + jpeg_size);
    tjFree(jpeg_buf);

    return result;
}

// 保存到文件
void save_jpeg(const std::string& path, const Image& img, int quality) {
    auto jpeg_data = encode_jpeg(img, quality);
    std::ofstream file(path, std::ios::binary);
    if (!file) throw std::runtime_error("Cannot open output file");
    file.write(reinterpret_cast<const char*>(jpeg_data.data()), jpeg_data.size());
}

main

cpp 复制代码
#include "image_scale.hpp"
#include <iostream>
#include <string>

int main() {
	try {
		// ============================================
		// 1. 读取 JPEG 文件(不缩放)
		// ============================================
		const std::string input_path = "C:\\image\\jpeg_image.jpg";
		Image original = read_jpeg(input_path);
		std::cout << "Original image: " << original.width << "x" << original.height
			<< " (DPI: " << original.dpi << ")\n";

		// ============================================
		// 2. 缩放操作(四种方法演示)
		// ============================================
		// 2.1 最近邻缩小 2 倍
		Image nearest = scale_image(
			original,
			{ original.width / 2, original.height / 2 },
			-1.0f, // 保持原DPI
			ScaleMethod::Nearest
		);

		// 2.2 双线性缩小到 400x300
		Image bilinear_400x300 = scale_image(
			original,
			{ 400, 300 },
			-1.0f,
			ScaleMethod::Bilinear
		);

		// 2.3 双三次缩放到 150 DPI(自动计算尺寸)
		float target_dpi = 150.0f;
		Image bicubic_150dpi = scale_image(
			original,
			{ 0, 0 }, // 自动计算尺寸
			target_dpi,
			ScaleMethod::Bicubic
		);
		std::cout << "Bicubic scaled to DPI " << target_dpi << ": "
			<< bicubic_150dpi.width << "x" << bicubic_150dpi.height << "\n";

		// 2.4 金字塔降采样缩小到 1/4 尺寸
		Image pyramid_quarter = scale_image(
			original,
			{ original.width / 4, original.height / 4 },
			-1.0f,
			ScaleMethod::Pyramid
		);

		// ============================================
		// 4. 编码与保存
		// ============================================
		// 4.1 保存为不同质量的 JPEG
		save_jpeg("C:\\image\\nearest.jpg", nearest, 95); // 高质量
		save_jpeg("C:\\image\\bilinear.jpg", bilinear_400x300, 95);
		save_jpeg("C:\\image\\bicubi.jpg", bicubic_150dpi, 95); // 低质量
		save_jpeg("C:\\image\\pyramid.jpg", pyramid_quarter, 95); // 低质量

		std::cout << "All operations completed successfully!\n";
	}
	catch (const std::exception& e) {
		std::cerr << "Fatal Error: " << e.what() << "\n";
		return 1;
	}
	return 0;
}
相关推荐
荼蘼1 小时前
基于 KNN 算法的手写数字识别项目实践
人工智能·算法·机器学习
Yuroo zhou1 小时前
IMU的精度对无人机姿态控制意味着什么?
单片机·嵌入式硬件·算法·无人机·嵌入式实时数据库
jackzhuoa2 小时前
java小白闯关记第一天(两个数相加)
java·算法·蓝桥杯·期末
Codeking__3 小时前
链表算法综合——重排链表
网络·算法·链表
minji...3 小时前
数据结构 堆(4)---TOP-K问题
java·数据结构·算法
AI_Keymaker4 小时前
一句话生成3D世界:腾讯开源混元3D模型
算法
Leon_vibs4 小时前
当 think 遇上 tool:深入解析 Agent 的规划之道
算法
旧时光巷4 小时前
【机器学习-2】 | 决策树算法基础/信息熵
算法·决策树·机器学习·id3算法·信息熵·c4.5算法
落了一地秋5 小时前
4.5 优化器中常见的梯度下降算法
人工智能·算法·机器学习
前端伪大叔5 小时前
第 5 篇:策略参数怎么调优?Freqtrade hyperopt 快速入门
算法·代码规范