金字塔降低采样

文章目录

    • image_scale.hpp
    • image_scale.cpp
    • main

image_scale.hpp

#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

#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. 初始化 TurboJPEGTJHandle 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");}// 压缩 JPEGuint8_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

#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, // 保持原DPIScaleMethod::Nearest);// 2.2 双线性缩小到 400x300Image 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 保存为不同质量的 JPEGsave_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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93084.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93084.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Filament引擎(四)——光照渲染Froxelizer实现分析

Froxelizer主要是用于filament光照效果的实现&#xff0c;生成光照渲染时所需的必要信息&#xff0c;帮助渲染过程中明确哪些区域受哪些光源所影响&#xff0c;是Filament中保证光照效果渲染效率的核心所在。这部分的源码&#xff0c;可以结合filament官方文档中Light Path部分…

2025 环法对决,VELO Angel Glide 坐垫轻装上阵

2025环法第16赛段的风秃山之巅&#xff0c;当最后一缕夕阳沉入云层&#xff0c;山风裹挟着砾石的气息掠过赛道&#xff0c;一场足以载入史册的激战正酣。帕雷-潘特的肌肉在汗水里贲张&#xff0c;链条与齿轮的咬合声混着粗重喘息&#xff0c;在171.5公里赛程的最后3公里陡坡上&…

Linux程序->进度条

进度条最终效果&#xff1a; 目录 进度条最终效果&#xff1a; 一&#xff1a;两个须知 1&#xff1a;缓冲区 ①&#xff1a;C语言自带缓冲区 ②&#xff1a;缓冲区的刷新策略 2&#xff1a;回车和换行的区别 二&#xff1a;倒计时程序 三&#xff1a;入门板进度条的实…

Python爬虫实战:研究tldextract库相关技术构建新闻网站域名分析爬虫系统

1. 引言 网络爬虫作为一种自动获取互联网信息的技术,在数据挖掘、信息检索、舆情分析等领域有着广泛的应用。Python 因其丰富的库和简洁的语法,成为了开发爬虫的首选语言。tldextract 是 Python 中一个强大的域名解析库,能够准确地从 URL 中提取顶级域名、二级域名等关键信…

【算法-华为机试-火星基地改造】

基地改造题目描述目标输入输出代码实现题目描述 在2XXX年&#xff0c;人们发现了一块火星地区&#xff0c;这里看起来很适合建设新家园。但问题是&#xff0c;我们不能一次性将这片地区的空气变得适合人类居住&#xff0c;得分步骤来。 把这片火星地区想象成一个巨大的棋盘。棋…

C++入门自学Day1-- C语言的宏函数和C++内联函数

一、函数调用开销函数调用会涉及&#xff1a;参数压栈&#xff08;或寄存器传参&#xff09;跳转到函数体返回值处理栈帧销毁这个过程对小函数来说可能非常浪费&#xff0c;因此&#xff0c;宏函数和内联函数的目的就是避免“函数调用的开销”&#xff0c;通过代码展开&#xf…

Pytorch混合精度训练最佳实践

混合精度训练&#xff08;Mixed Precision Training&#xff09;是一种通过结合单精度&#xff08;FP32&#xff09;和半精度&#xff08;FP16/FP8&#xff09;计算来加速训练、减少显存占用的技术。它在保持模型精度的同时&#xff0c;通常能带来 2-3 倍的训练速度提升&#x…

Qt C++动态库SDK在Visual Studio 2022使用(C++/C#版本)

01 将C SDK 集成到 IDE 中以下是在 Microsoft Visual Studio 平台下 SDK 的集成。2.1 Visual Studio 平台下 C/C环境配置及集成到 IDE 中xxx.lib 和 xxx.dll 适合在 Windows 操作系统平台使用&#xff0c;这里以 VS2022 环境为例。2.1.1 C/C 工程环境配置与集成1、C# SDK 接口…

大语言模型 LLM 通过 Excel 知识库 增强日志分析,根因分析能力的技术方案(2):LangChain + LlamaIndex 实现

文章大纲 1 技术原理总览 2 详细实现步骤(含代码) 2.1 环境准备 2.2 Excel → LlamaIndex 节点 2.3 构建向量索引(FAISS 本地) 2.4 Google Cloud 向量检索(可选替换 FAISS) 2.5 LangChain 问答链 A. RAG 模式(向量检索 + LLM 生成) B. SQL 模式(无 RAG,直接查表) 2.…

提升ARM Cortex-M系统性能的关键技术:TCM技术解析与实战指南

文章目录引言一、TCM基础架构与工作原理1.1 TCM的物理特性1.2 与缓存机制的对比1.3 ARM Cortex-M系列对TCM的支持二、TCM的典型应用场景2.1 实时中断处理2.2 低功耗模式下的待机代码2.3 高性能算法执行2.4 系统初始化阶段的关键代码三、实战指南&#xff1a;在STM32H7上配置和优…

大数据之路:阿里巴巴大数据实践——大数据领域建模综述

为什么需要数据建模 核心痛点 数据冗余&#xff1a;不同业务重复存储相同数据&#xff08;如用户基础信息&#xff09;&#xff0c;导致存储成本激增。计算资源浪费&#xff1a;未经聚合的明细数据直接参与计算&#xff08;如全表扫描&#xff09;&#xff0c;消耗大量CPU/内存…

实战演练1:实战演练之命名实体识别

实战演练1:实战演练之命名实体识别 命名实体识别简介 代码 命名实体识别简介 什么是命名实体识别任务 命名实体识别(Named Entity Recognition,简称NER)是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。通常包括两部分: (1)实体边界识别。(2)确定…

数据结构基础内容(第七篇:堆、哈夫曼树)

# 堆 Heap 优先队列(Priority Queue) 结构性:用 *数组* 表示的完全二叉树; 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值) * “最大堆(MaxHeap)”,也称“大顶堆”:最大值 * “最小堆(MinHeap)”,也称“小顶堆” :最小值 主要操作有: • MaxHeap Create( i…

CS231n-2017 Lecture7训练神经网络(二)笔记

本节主要是神经网络的动态部分&#xff0c;也就是神经网络学习参数和搜索最优超参数的过程梯度检查&#xff1a;进行梯度检查&#xff0c;就是简单地把解析梯度与数值计算梯度进行比较&#xff0c;防止反向传播的逻辑出错&#xff0c;仅在调试过程中使用。有如下技巧 &#xff…

IntelliJ IDEA 中左上方未显示项目根目录问题

问题&#xff1a; 在IDEA中编写代码时&#xff0c;发现左上方只显示项目的子模块&#xff0c;未显示根项目名称。 如图所示&#xff0c;未显示子模块的根项目&#xff1a;问题分析 顶层根目录未被识别为项目根目录&#xff0c;需要手动添加识别。 问题解决 进入File – Project…

OpenCV 图像变换全解析:从镜像翻转到仿射变换的实践指南

前言处理图像时&#xff0c;翻转、旋转、平移等操作很常用。OpenCV 提供了简单的方法实现这些变换&#xff0c;本文带你快速学会用它做图像翻转和仿射变换。1 图像翻转(图像镜像旋转)在OpenCV中&#xff0c;图片的镜像旋转是以图像的中心为原点进行镜像翻转的。cv2.flip(img,fl…

【运维】Linux运维命令记录

重置root密码使用命令重新设置一下root账户的密码 passwd root根据提示设置一下密码&#xff0c;然后使用sudo -i 时输入密码就可以切换到root账户了ssh登陆以后&#xff0c;要用sudo -i命令给用户提权&#xff0c;提到超级管理员&#xff0c;然后输入密码才有用

PandasAI连接LLM进行智能数据分析

1. 引言 Pandas是一个数据分析开源组件库&#xff0c;提供了高性能、易用的数据结构和数据分析工具。它的核心的功能是其DataFrame对象&#xff0c;这是一个带有行和列标签的二维表格数据结构&#xff0c;支持缺失数据处理、时间序列功能、灵活的数据输入输出方法、数据对齐和…

Spring之【Bean的生命周期】

目录 1、生成BeanDefinition BeanDefinitionRegistry接口 DefaultListableBeanFactory实现类 2、合并BeanDefnition AbstractBeanFactory类 3、BeanFactoryPostProcessor的方法回调 AbstractApplicationContext类 PostProcessorRegistrationDelegate类 4、BeanPostPro…

搜狐新闻直播间适配HarmonyOs实现点赞动画

01背景介绍随着新闻客户端鸿蒙单框架系统适配工作的推进&#xff0c;从原来的基础功能到现在已经适配全功能的85%以上。与此同时&#xff0c;我们也在持续深入挖掘鸿蒙系统的特性&#xff0c;以提升整体应用的质量与用户体验。在这一过程中&#xff0c;动画作为增强交互与视觉体…