金字塔降低采樣

文章目錄

    • 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
英文地址,請注明出處:http://en.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;動畫作為增強交互與視覺體…