当前位置: 首页 > news >正文

解码Linux文件IO之JPEG图像原理与应用

JPEG 基础概念

  • JPEG 的双重身份

    • 既是图像压缩编码标准:由联合图像专家组(Joint Photographic Experts Group)制定,1992 年发布,面向「连续色调静止图像」(如照片、风景图,含渐变色彩的静态图)。

    • 也是图像文件格式:文件扩展名为 .jpg.jpeg,两者完全等价(可互相重命名,文件内容不变)。

      image

  • 核心优势:高压缩比JPEG 是有损压缩格式,在保证视觉效果接近原图的前提下,文件体积远小于 BMP(无压缩)等格式。例如一张 1024×768 的照片,BMP 约 2.25MB,JPEG 仅需 100-300KB,因此特别适合网络传输和移动设备存储。

    image

  • 局限性因有损压缩,反复编解码会导致画质退化;不适合存储文字、图标等「像素级精确」的图像(易出现边缘模糊)。

核心编解码库:libjpeg

要处理 JPEG 文件(解码显示 / 编码生成),需使用专门的库 ——libjpeg,原因是 JPEG 文件经过压缩,无法像 BMP 那样直接读取像素数据。

image

libjpeg 关键特性

  • 开源免费:由独立 JPEG 小组(IJG)维护,官网:www.ijg.org。

  • 语言与兼容性:纯 C 语言实现,跨平台(Linux、Windows、嵌入式系统等),支持 JPEG 标准的所有核心功能(压缩、解码、渐进式显示等)。

  • 工业级应用:许多知名库 / 工具的底层依赖 libjpeg,例如 OpenCV(计算机视觉库)读取 JPEG 图像时,就是通过 libjpeg 完成解码。

    image

  • 非系统标准库:Linux、Windows 等系统默认不预装 libjpeg,需手动移植或安装后才能使用。

libjpeg 库移植(Linux 环境)

移植目的:将 libjpeg 源码编译为目标平台(如 ARM 开发板)可使用的库文件(动态库 .so 或静态库 .a),步骤分「下载→解压→配置→编译→安装」5 步。

步骤 1:下载源码

  • 官网下载最新稳定版,例如 jpegsrc.v9f.tar.gz(2024 年计划发布的稳定版,当前最新稳定版为 9e)。
  • 注意:选择「Unix 格式压缩包(.tar.gz)」,避免 Windows 格式(.zip)。

步骤 2:解压源码

  • 将压缩包传到 Linux 系统的非共享文件夹(共享文件夹可能因权限问题导致编译错误),例如 /home/lib/libjpeg_dir ,解压后找到自述文件README,打开README了解libjpeg库的使用规则。

  • 执行解压命令:

    # 格式:tar zxf 压缩包名
    tar zxf jpegsrc.v9f.tar.gz
    
  • 解压后生成文件夹 jpeg-9f(源码目录)。

步骤 3:配置编译参数

配置的核心是指定「安装路径」和「目标平台」,通过源码目录下的 configure 脚本实现。

  • 先创建安装目录(用于存放编译后的库文件和头文件),建议与源码目录同级,例如:

    # 创建安装目录 libjpeg(绝对路径:/home/lib/libjpeg_dir/libjpeg)
    mkdir /home/lib/libjpeg_dir/libjpeg
    
  • 进入源码目录,执行配置命令:

    cd jpeg-9f  # 进入源码目录
    # 配置命令:--prefix 指定安装路径(必须绝对路径),--host 指定目标平台(ARM 开发板用 arm-linux)
    ./configure --prefix=/home/lib/libjpeg_dir/libjpeg --host=arm-linux
    
  • 配置成功标志:生成 Makefile 脚本(后续编译依赖此文件)和 jconfig.h(配置头文件)。

步骤 4:编译源码

  • 执行 make 命令,自动读取 Makefile 编译源码:

    make
    
  • 注意:编译过程中若出现错误(如「未定义函数」「权限不足」),需先解决错误(如安装依赖、检查配置参数),再重新编译。编译成功无报错,会生成 cjpeg(JPEG 编码器)、djpeg(JPEG 解码器)等工具,以及库文件的中间文件。

步骤 5:安装库文件

  • 执行 make install,将编译后的库文件、头文件安装到步骤 3 指定的 -prefix 路径:

    bash

    make install
    
  • 安装后目录结构(关键文件):

    目录内容说明
    include/头文件:jpeglib.h(核心头文件,程序需包含)、jerror.h(错误处理)等
    lib/库文件:libjpeg.so(动态库,开发板运行需依赖)、libjpeg.a(静态库)
    bin/工具:cjpeg(BMP 转 JPEG)、djpeg(JPEG 转 BMP)等

移植后准备

include/lib/ 文件夹拷贝到自己的工程目录(如 project/),与源码文件(main.c)同级,方便后续开发维护:

project/
├─ include/  # 从安装目录拷贝的头文件
├─ lib/      # 从安装目录拷贝的库文件
└─ main.c    # 自己的 JPEG 处理代码

libjpeg 核心应用:JPEG 解码(LCD 显示必备)

要在 LCD 上显示 JPEG 图像,需先将 JPEG 压缩数据解码为「像素 RGB 分量」,再将 RGB 数据写入 LCD 显存。解码流程是开发核心,需理解并掌握,共 8 步,每步配完整代码 + 详细注释。

解码核心原理

JPEG 解码本质:将压缩的「DCT 系数、量化表」等数据,反向转换为「像素的 RGB 或 YCbCr 分量」(最终需转 RGB 适配 LCD)。

完整解码代码实现

#include <stdio.h>
#include <stdlib.h>
#include <jpeglib.h>  // libjpeg 核心头文件,必须包含
// 函数:解码 JPEG 文件,返回 RGB 像素数据(二维数组:[行][列×3],3 代表 R、G、B 分量)
// 参数:
//   filename:待解码的 JPEG 文件路径(如 "./pic/test.jpg")
//   width:输出参数,用于存储解码后图像的宽度(像素数)
//   height:输出参数,用于存储解码后图像的高度(像素数)
// 返回值:
//   成功:RGB 像素数据指针(需手动 free 释放)
//   失败:NULL
unsigned char* jpeg_decode(const char* filename, int* width, int* height) {// -------------------------- 步骤 1:创建解码对象与错误处理对象 --------------------------// 定义 JPEG 解码结构体(存储解码过程的所有信息)和错误处理结构体struct jpeg_decompress_struct cinfo;struct jpeg_error_mgr jerr;  // 错误处理对象,用于捕获解码中的错误// 关联错误处理对象:将错误处理结构体绑定到解码对象,让解码错误能被捕获cinfo.err = jpeg_std_error(&jerr);// 创建解码对象:初始化解码结构体,分配内部资源// 参数:&cinfo - 解码结构体指针(必须非 NULL)// 返回值:无(若失败会通过 jerr 触发错误)jpeg_create_decompress(&cinfo);// -------------------------- 步骤 2:打开 JPEG 文件并绑定到解码对象 --------------------------// 以二进制方式打开文件(JPEG 是二进制文件,必须用 "rb",避免文本模式转换换行符)FILE* infile = fopen(filename, "rb");if (infile == NULL) {fprintf(stderr, "错误:无法打开文件 %s\n", filename);jpeg_destroy_decompress(&cinfo);  // 打开失败,先释放解码对象return NULL;}// 将文件指针绑定到解码对象:告诉 libjpeg 从哪个文件读取压缩数据// 参数://   &cinfo - 解码结构体指针//   infile - 已打开的文件指针(必须是 "rb" 模式)// 返回值:无jpeg_stdio_src(&cinfo, infile);// -------------------------- 步骤 3:读取 JPEG 文件头,获取图像信息 --------------------------// 读取文件头(包含图像宽、高、色彩空间等信息),并将信息存入 cinfo// 参数://   &cinfo - 解码结构体指针//   TRUE   - 强制读取完整文件头(若为 FALSE,仅读取部分信息)// 返回值://   JPEG_HEADER_OK - 读取成功//   其他值 - 读取失败(如文件不是 JPEG 格式)jpeg_read_header(&cinfo, TRUE);// 从 cinfo 中提取图像宽高,赋值给输出参数*width = cinfo.image_width;    // 解码后图像宽度(像素)*height = cinfo.image_height;  // 解码后图像高度(像素)printf("JPEG 图像信息:宽=%d 像素,高=%d 像素\n", *width, *height);// -------------------------- 步骤 4:(可选)设置解码参数 --------------------------// 若用默认参数(如输出 RGB 色彩、不缩放图像),此步骤可省略// 示例:设置解码后输出 RGB 格式(默认可能为 YCbCr,需手动指定)cinfo.out_color_space = JCS_RGB;  // JCS_RGB 表示输出 RGB 分量cinfo.output_components = 3;      // 每个像素 3 个分量(R、G、B)// -------------------------- 步骤 5:开始解码 --------------------------// 初始化解码流程,准备读取像素数据(需在读取扫描线前调用)// 参数:&cinfo - 解码结构体指针// 返回值:无(若失败触发错误)jpeg_start_decompress(&cinfo);// -------------------------- 步骤 6:循环读取每行像素数据 --------------------------// 计算每行像素的字节数:宽度 × 每个像素分量数(RGB 为 3)int row_stride = *width * 3;  // 一行数据的总字节数// 分配缓冲区:存储一行像素数据(因 libjpeg 推荐每次读一行,避免内存浪费)// 缓冲区类型为 unsigned char*[](二维数组,每行一个指针)unsigned char* buffer[1];  // buffer[0] 指向一行数据的首地址buffer[0] = (unsigned char*)malloc(row_stride);  // 分配一行内存if (buffer[0] == NULL) {fprintf(stderr, "错误:无法分配行缓冲区\n");jpeg_abort_decompress(&cinfo);  // 终止解码fclose(infile);jpeg_destroy_decompress(&cinfo);return NULL;}// 分配总像素缓冲区:存储整个图像的 RGB 数据(高度 × 每行字节数)unsigned char* rgb_data = (unsigned char*)malloc(*height * row_stride);if (rgb_data == NULL) {fprintf(stderr, "错误:无法分配 RGB 总缓冲区\n");free(buffer[0]);jpeg_abort_decompress(&cinfo);fclose(infile);jpeg_destroy_decompress(&cinfo);return NULL;}// 循环读取每行数据:从 JPEG 中读一行,存入 buffer,再拷贝到 rgb_dataint row_idx = 0;  // 当前读取的行号(从 0 开始)// 循环条件:当前已读行数(cinfo.output_scanline) < 总高度(cinfo.output_height)while (cinfo.output_scanline < cinfo.output_height) {// 读取一行像素数据到 buffer// 参数://   &cinfo - 解码结构体指针//   buffer - 缓冲区指针(二维数组,每个元素指向一行)//   1      - 最大读取行数(这里设为 1,每次读一行)// 返回值:实际读取的行数(正常为 1,到最后一行可能小于 1)int read_rows = jpeg_read_scanlines(&cinfo, buffer, 1);if (read_rows > 0) {// 将当前行数据拷贝到总缓冲区的对应位置memcpy(rgb_data + row_idx * row_stride, buffer[0], row_stride);row_idx++;  // 行号自增}}// -------------------------- 步骤 结束解码 --------------------------// 释放解码过程中的临时资源(如量化表、DCT 系数缓冲区)// 参数:&cinfo - 解码结构体指针// 返回值:无jpeg_finish_decompress(&cinfo);// -------------------------- 步骤 释放资源 --------------------------free(buffer[0]);         // 释放行缓冲区jpeg_destroy_decompress(&cinfo);  // 销毁解码对象,释放所有内部资源fclose(infile);          // 关闭文件// 返回解码后的 RGB 数据return rgb_data;
}// 主函数:测试解码功能
int main() {const char* jpeg_path = "./pic/test.jpg";  // JPEG 文件路径int img_width, img_height;                 // 图像宽高unsigned char* rgb = NULL;                 // 存储解码后的 RGB 数据// 调用解码函数rgb = jpeg_decode(jpeg_path, &img_width, &img_height);if (rgb == NULL) {fprintf(stderr, "解码失败\n");return -1;}// -------------------------- 后续操作:将 RGB 写入 LCD --------------------------// 此处省略 LCD 写入代码(需根据 LCD 驱动接口实现)// 核心逻辑:遍历 rgb 数组,将每个像素的 R、G、B 分量写入 LCD 对应坐标的显存printf("解码成功,RGB 数据大小:%d 字节(%d × %d × 3)\n", img_width * img_height * 3, img_width, img_height);// 释放 RGB 数据(使用完后必须释放,避免内存泄漏)free(rgb);return 0;
}

JPEG 编码流程

BMP 转 JPEG,需掌握 libjpeg 编码流程(核心 7 步),此处给出关键步骤与代码框架:

// 函数:将 BMP 的 RGB 数据编码为 JPEG 文件
// 参数:
//   rgb_data:BMP 解码后的 RGB 数据([行][列×3])
//   width:图像宽度(像素)
//   height:图像高度(像素)
//   jpeg_path:输出 JPEG 文件路径
//   quality:压缩质量(1-100,100 为最高质量,最小压缩)
// 返回值:0 成功,-1 失败
int bmp_to_jpeg(unsigned char* rgb_data, int width, int height, const char* jpeg_path, int quality) {// 步骤 1:创建编码对象与错误处理对象struct jpeg_compress_struct cinfo;struct jpeg_error_mgr jerr;cinfo.err = jpeg_std_error(&jerr);jpeg_create_compress(&cinfo);// 步骤 2:创建输出文件并绑定到编码对象FILE* outfile = fopen(jpeg_path, "wb");if (outfile == NULL) {fprintf(stderr, "无法创建 JPEG 文件\n");jpeg_destroy_compress(&cinfo);return -1;}jpeg_stdio_dest(&cinfo, outfile);// 步骤 3:设置编码参数(图像宽高、色彩空间、压缩质量)cinfo.image_width = width;          // 输入图像宽度cinfo.image_height = height;        // 输入图像高度cinfo.input_components = 3;         // 输入分量数(RGB 为 3)cinfo.in_color_space = JCS_RGB;     // 输入色彩空间(RGB)jpeg_set_defaults(&cinfo);          // 设置默认编码参数jpeg_set_quality(&cinfo, quality, TRUE);  // 设置压缩质量// 步骤 4:开始编码jpeg_start_compress(&cinfo, TRUE);// 步骤 5:循环写入每行 RGB 数据int row_stride = width * 3;         // 每行字节数unsigned char* buffer[1];buffer[0] = (unsigned char*)malloc(row_stride);if (buffer[0] == NULL) {fprintf(stderr, "分配缓冲区失败\n");jpeg_abort_compress(&cinfo);fclose(outfile);jpeg_destroy_compress(&cinfo);return -1;}int row_idx = 0;while (cinfo.next_scanline < cinfo.image_height) {// 从 RGB 总数据中拷贝一行到缓冲区(注意 BMP 可能是倒序,需调整行号)memcpy(buffer[0], rgb_data + (height - 1 - row_idx) * row_stride, row_stride);// 写入一行数据到 JPEG 文件jpeg_write_scanlines(&cinfo, buffer, 1);row_idx++;}// 步骤 6:结束编码jpeg_finish_compress(&cinfo);// 步骤 7:释放资源free(buffer[0]);jpeg_destroy_compress(&cinfo);fclose(outfile);return 0;
}

程序编译与开发板调试

编译命令解析

因 libjpeg 是第三方库,编译器默认找不到其头文件和库文件,需通过选项手动指定:

# 编译命令格式(ARM 开发板交叉编译)
arm-linux-gcc main.c -o jpeg_display -I ./include -L ./lib -ljpeg

各选项含义:

  • I ./include:指定头文件路径(./include 是工程中 libjpeg 头文件所在目录);
  • L ./lib:指定库文件路径(./lib 是工程中 libjpeg 库文件所在目录);
  • ljpeg:指定链接的库名(libjpeg.solibjpeg.a,省略前缀 lib 和后缀 .so/.a);
  • jpeg_display:生成的可执行文件名。

开发板调试注意事项

  • 动态库依赖:若使用动态库(libjpeg.so),需将 lib/libjpeg.so.9(或对应版本)拷贝到开发板的 /lib 目录:

    # 示例:通过 scp 拷贝动态库到开发板(假设开发板 IP 为 192.168.1.100)
    scp ./lib/libjpeg.so.9 root@192.168.1.100:/lib/
    
  • 路径问题:JPEG 文件路径需与开发板上的实际路径一致(如开发板上 JPEG 存于 /root/pic/test.jpg,代码中路径需改为该地址);

  • LCD 越界检查:在 LCD 显示时,需确保图像位置(x, y)+ 图像宽高 ≤ LCD 分辨率(如 LCD 为 800×480,则 x+width ≤ 800,y+height ≤ 480)。

实战注意事项

LCD 任意位置显示 JPEG

  • 完成 libjpeg 库移植;
  • 编写代码:调用 jpeg_decode 函数获取 RGB 数据,再调用 LCD 驱动函数(如 lcd_draw_pixel)将 RGB 数据写入指定位置;
  • 关键注意点:
    • 计算显示坐标:若要在(x0, y0)位置显示,需遍历 RGB 数据时,像素坐标为(x0 + x, y0 + y)(x 为 0~width-1,y 为 0~height-1);
    • 越界判断:必须确保 x0 + width ≤ LCD_WIDTHy0 + height ≤ LCD_HEIGHT,否则会写入 LCD 显存非法区域,导致显示错乱。

BMP 转 JPEG

  • 先解码 BMP 文件:读取 BMP 头文件,提取宽高和 RGB 数据(BMP 存储为「下到上」,需反转行顺序);
  • 调用 bmp_to_jpeg 函数将 RGB 数据编码为 JPEG;
  • 测试验证:用 djpeg 工具(libjpeg 自带)将生成的 JPEG 转回 BMP,对比与原 BMP 的差异(因 JPEG 有损,会有细微差异)。

常见问题解决

  • 编译报错「找不到 jpeglib.h」:检查 I 选项是否指定正确的头文件路径,确保 include/ 下有 jpeglib.h
  • 开发板运行报错「error while loading shared libraries: libjpeg.so.9: cannot open shared object file」:未将动态库拷贝到开发板 /lib 目录,或拷贝的版本不匹配;
  • 解码后图像颠倒:JPEG 解码后行顺序为「上到下」,若 LCD 要求「下到上」,需反转 RGB 数据的行顺序(如 rgb_data + (height - 1 - y) * row_stride);
  • 编码后 JPEG 色彩异常:检查输入色彩空间是否为 JCS_RGB,确保 BMP 转 RGB 时未混淆 R、G、B 分量顺序。
http://www.dtcms.com/a/524629.html

相关文章:

  • “短小精悍”的边缘AI算力利器:超微SYS-E403-14B-FRN2T服务器评测
  • Gradio全解14——使用Gradio构建MCP的服务器与客户端(4)——Python包命令:uv与uvx实战
  • php是网站开发的语言吗怎么做好市场宣传和推广
  • 做cms网站步骤广东手机网站建设哪家好
  • GreatSQL 配置 SSL 访问:单机与 MGR 集群指南
  • 网站开发进度把握网站备案需要拍照
  • LC104 二叉树的最大深度
  • 如何构建企业级数据分析助手:Data Agent 开发实践
  • 网站内容 优化网站维护做啥的
  • Diffusion-TS:一种基于季节性-趋势分解与重构引导的可解释时间序列扩散模型​
  • LabVIEW连接本地部署大模型
  • Dart Sass 弃用警告修复教程:Deprecation Warning [global-builtin] 详解与解决方案
  • 专门做杂志的网站有哪些怎么给网站添加站点统计
  • Rust并发编程:免死金牌与实战
  • OkHttp连接复用
  • 返利网站程序wordpress导出出错
  • 网站外部优化郑州网站建设定制开发
  • 无线图传模块:引领科技未来的创新突破
  • 构建全栈JavaScript应用:Express与React的高效开发实践
  • 威海网站建设是什么免费网页空间
  • USB2.0枚举流程(以鼠标为例)——从零开始学习USB2.0协议(四)
  • hot100练习-17
  • 光伏发电建模与性能分析:从半导体物理到输出功率预测
  • 浙江正规网站建设配件网站seo优化分析
  • 设计师赚钱的网站创新的常州做网站
  • vue3的props的使用
  • 【Trae+AI】和Trae学习搭建App_03:后端API开发原理与实践(已了解相关知识的可跳过)
  • List of Keys (Keyboard,Mouse and Controller)
  • 门户网站怎样做wordpress清新模板
  • 沈阳有资质做网站的公司公司自有网站工信备案