在嵌入式 Linux 开发中,JPEG 是最常用的图像格式之一,凭借高压缩比、小文件体积的优势广泛应用于网络传输、LCD 显示等场景。但 JPEG 文件为压缩格式,无法直接读取像素数据,需借助libjpeg 开源库完成编解码操作。本文基于实战笔记,从libjpeg 库的交叉编译移植JPEG 图像解压(解码)JPEG 图像压缩(编码) 三个核心部分展开,附带完整可运行代码,手把手教你实现 JPEG 图像的处理与 LCD 显示。

一、libjpeg 库基础认知

1.1 什么是 libjpeg

libjpeg 是由 IJG(Independent JPEG Group)维护的开源 JPEG 编解码库,完全由 C 语言实现,集成了成熟的 JPEG 压缩 / 解压算法,是 OpenCV 等图像处理库的底层依赖,支持将 JPEG 文件解码为 RGB 像素数据,也能将 BMP 等位图编码为 JPEG 压缩文件。

1.2 核心特性

  • 支持 JPEG 标准的各种压缩 / 解压模式,可自定义压缩质量;
  • 跨平台,支持 Linux/Windows/ 嵌入式系统;
  • 提供简洁的 C 语言 API,易集成到工程中;
  • 支持彩色 / 灰度图像处理,输出 RGB/YUV 等颜色格式。

1.3 库文件说明

移植完成后会生成两类核心文件,后续开发需用到:

  • 头文件jpeglib.hjerror.hjconfig.hjmorecfg.h(包含编解码 API 和数据结构);
  • 库文件:静态库libjpeg.a、动态库libjpeg.so/libjpeg.so.9/libjpeg.so.9.6.0(开发时链接,运行时加载)。

二、libjpeg 库的交叉编译移植(ARM-Linux)

由于 libjpeg 并非 Linux 系统标准库,且嵌入式开发需在x86 主机编译出ARM 目标平台的库文件,因此需要进行交叉编译移植。本次移植基于libjpeg-9f版本,目标平台为 ARM-Linux(如 6818 开发板),主机为 Ubuntu 18.04/20.04。

2.1 移植准备

  1. 下载源码:从 IJG 官网下载最新版源码包,地址:https://www.ijg.org/,本次使用jpegsrc.v9f.tar.gz
  2. 交叉编译工具链:已安装 ARM-Linux 交叉编译工具链(如arm-linux-gcc),并配置好环境变量;
  3. 主机环境:Ubuntu 系统安装makegcctar等基础工具(sudo apt install make gcc tar)。

2.2 移植步骤(图文详解)

步骤 1:解压源码包

将下载的jpegsrc.v9f.tar.gz上传到 Ubuntu 主机(避免共享文件夹,防止权限 / 路径问题),执行解压命令:

# 解压到当前目录
tar zxf jpegsrc.v9f.tar.gz
# 进入解压后的目录
cd jpeg-9f/
步骤 2:创建安装目录

创建独立的安装目录,用于存放移植后的头文件、库文件,方便后续拷贝到工程 / 开发板:

# 上级目录创建libjpeg安装目录(绝对路径)
mkdir -p /home/lin/teacher/libjpeg_dir/libjpeg

注意:安装目录必须使用绝对路径,后续配置时会用到。

步骤 3:配置交叉编译(核心)

执行./configure脚本进行配置,指定安装路径目标平台,核心参数:

  • --prefix:指定库的安装目录(绝对路径);
  • --host:指定目标平台,ARM-Linux 为arm-linux
  • 自动生成适用于 ARM 平台的Makefile脚本。

执行配置命令:

./configure --prefix=/home/lin/teacher/libjpeg_dir/libjpeg --host=arm-linux

配置报错解决:若提示arm-linux-gcc: command not found,说明交叉编译工具链未配置环境变量,需将工具链的bin目录添加到PATH

export PATH=/home/lin/arm/5.4.0/bin:$PATH
步骤 4:编译源码

执行make命令编译源码,编译过程中会生成各类工具(cjpeg/djpeg)和库文件,确保无报错

# 直接编译,多核编译可加-j4(4核)加速
make

编译报错解决:若提示头文件缺失,检查交叉编译工具链是否完整;若提示权限问题,加sudo重新编译。

步骤 5:安装库文件

执行make install将头文件、库文件、工具安装到之前指定的--prefix目录:

make install
步骤 6:查看移植结果

进入安装目录,查看生成的核心文件,移植完成:

# 进入安装目录
cd /home/lin/teacher/libjpeg_dir/libjpeg/
# 查看目录结构
ls

目录结构说明:

  • bin/:jpeg 编解码工具(cjpeg:BMP 转 JPEG,djpeg:JPEG 转 BMP);
  • include/:编解码所需头文件(核心:jpeglib.h);
  • lib/:静态库 + 动态库(核心:libjpeg.a、libjpeg.so.9.6.0);
  • share/:帮助文档和手册。

2.3 移植后文件使用

include/lib/文件夹拷贝到自己的嵌入式工程目录中,与源码文件放在同一级,方便后续编译时指定路径;若需在开发板运行程序,需将动态库libjpeg.so.9拷贝到开发板的/lib/usr/lib目录(开发板运行时依赖)。

三、JPEG 图像解压(解码)实战 —— 显示到 LCD

JPEG 解压的核心是将压缩的 JPEG 文件解码为RGB 像素数据,然后将 RGB 数据写入 LCD 屏的像素缓冲区,实现图像显示。解码流程是 libjpeg 使用的核心,需牢记步骤,且所有步骤均基于 libjpeg 提供的 API 实现。

3.1 解码核心流程(必须背会)

  1. 创建并初始化 JPEG 解压对象,关联错误处理对象;
  2. 以二进制方式打开 JPEG 文件,将文件指针绑定到解压对象;
  3. 读取 JPEG 文件头,获取图像宽、高、色深等信息;
  4. 设置解压参数(可选,默认参数即可满足大部分需求);
  5. 开始解压,初始化解压内部状态;
  6. 循环读取每行的 RGB 像素数据,写入 LCD 屏;
  7. 完成解压,释放解压过程的临时内存;
  8. 销毁 JPEG 解压对象,关闭文件,释放所有资源。

3.2 关键 API 说明

函数名 功能
jpeg_create_decompress(&cinfo) 创建并初始化 JPEG 解压对象
jpeg_std_error(&jerr) 初始化错误处理对象,关联到解压对象
jpeg_stdio_src(&cinfo, infile) 将文件指针绑定到解压对象,指定解压源
jpeg_read_header(&cinfo, TRUE) 读取 JPEG 文件头,获取图像信息
jpeg_start_decompress(&cinfo) 开始解压,初始化内部状态
jpeg_read_scanlines(&cinfo, buf, 1) 读取 1 行像素数据到缓冲区
jpeg_finish_decompress(&cinfo) 完成解压,释放临时内存
jpeg_destroy_decompress(&cinfo) 销毁解压对象,释放所有资源

核心数据结构

// JPEG解压对象(核心)
struct jpeg_decompress_struct cinfo;
// 错误处理对象
struct jpeg_error_mgr jerr;

解压对象cinfo包含图像的所有信息:

  • cinfo.output_width:解码后图像宽度(像素);
  • cinfo.output_height:解码后图像高度(像素);
  • cinfo.output_components:每个像素的颜色分量数(RGB 为 3,灰度为 1)。

3.3 完整解码代码(JPEG 显示到 LCD)

本次代码实现在 LCD 指定位置显示任意大小的 JPEG 图片,适配 320240/800480 等 LCD 屏,核心是将解码后的 RGB 数据写入 LCD 的帧缓冲区(/dev/fb0),无 LCD 屏可注释掉 LCD 相关代码,仅打印图像信息

3.3.1 代码整体结构

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <jpeglib.h>
#include <jerror.h>

// LCD屏参数(根据实际屏修改,示例:800*480,32位色深)
#define LCD_WIDTH  800
#define LCD_HEIGHT 480
#define LCD_BPP    32
#define LCD_SIZE   (LCD_WIDTH * LCD_HEIGHT * LCD_BPP / 8)

// 全局变量:LCD帧缓冲区地址
unsigned int *lcd_buf = NULL;

// 初始化LCD,映射帧缓冲区
int lcd_init(void)
{
    int fd = open("/dev/fb0", O_RDWR);
    if (fd < 0)
    {
        perror("open lcd failed");
        return -1;
    }
    // 映射帧缓冲区到用户空间
    lcd_buf = (unsigned int *)mmap(NULL, LCD_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (lcd_buf == MAP_FAILED)
    {
        perror("mmap lcd failed");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

// 释放LCD映射
void lcd_uninit(void)
{
    munmap(lcd_buf, LCD_SIZE);
}

// RGB565转RGB888(LCD32位色深,ARGB8888)
// jpeg解码后为RGB888,转换为LCD所需的像素格式
unsigned int rgb888_to_argb8888(unsigned char r, unsigned char g, unsigned char b)
{
    return (0xFF << 24) | (r << 16) | (g << 8) | b;
}

// 在LCD指定位置显示JPEG图片
// x0,y0:图片在LCD的左上角坐标;jpg_path:JPEG文件路径
int jpeg_display_lcd(int x0, int y0, const char *jpg_path)
{
    // 1. 定义解压对象和错误处理对象
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE *infile = NULL;
    JSAMPARRAY buffer = NULL;  // 存储每行的像素数据
    int row_stride = 0;        // 每行的字节数 = 宽度 * 颜色分量数
    int x, y;
    unsigned char *rgb_data = NULL;

    // 检查坐标是否越界
    if (x0 < 0 || x0 >= LCD_WIDTH || y0 < 0 || y0 >= LCD_HEIGHT)
    {
        printf("LCD coordinate out of range\n");
        return -1;
    }

    // 2. 初始化错误处理对象,关联到解压对象
    cinfo.err = jpeg_std_error(&jerr);
    // 3. 创建并初始化解压对象
    jpeg_create_decompress(&cinfo);

    // 4. 以二进制方式打开JPEG文件
    infile = fopen(jpg_path, "rb");
    if (infile == NULL)
    {
        perror("fopen jpg failed");
        jpeg_destroy_decompress(&cinfo);
        return -1;
    }
    // 将文件指针绑定到解压对象
    jpeg_stdio_src(&cinfo, infile);

    // 5. 读取JPEG文件头,获取图像信息
    jpeg_read_header(&cinfo, TRUE);
    printf("JPEG image info: width=%d, height=%d, components=%d\n",
           cinfo.image_width, cinfo.image_height, cinfo.num_components);

    // 6. 设置解压参数(默认参数,RGB888输出)
    cinfo.out_color_space = JCS_RGB;  // 输出RGB颜色空间
    // 7. 开始解压
    jpeg_start_decompress(&cinfo);

    // 计算每行的字节数和图片显示的最大坐标(防止越界)
    row_stride = cinfo.output_width * cinfo.output_components;
    int x_max = x0 + cinfo.output_width > LCD_WIDTH ? LCD_WIDTH : x0 + cinfo.output_width;
    int y_max = y0 + cinfo.output_height > LCD_HEIGHT ? LCD_HEIGHT : y0 + cinfo.output_height;
    // 分配行缓冲区(存储1行的RGB数据)
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);

    // 8. 循环读取每行像素数据,写入LCD
    y = y0;
    while (cinfo.output_scanline < cinfo.output_height && y < y_max)
    {
        // 读取1行数据到buffer
        jpeg_read_scanlines(&cinfo, buffer, 1);
        rgb_data = buffer[0];
        // 逐像素写入LCD
        x = x0;
        for (int i = 0; i < row_stride && x < x_max; i += 3)
        {
            unsigned char r = rgb_data[i];
            unsigned char g = rgb_data[i+1];
            unsigned char b = rgb_data[i+2];
            // 写入LCD帧缓冲区,计算像素位置
            lcd_buf[y * LCD_WIDTH + x] = rgb888_to_argb8888(r, g, b);
            x++;
        }
        y++;
    }

    // 9. 完成解压
    jpeg_finish_decompress(&cinfo);
    // 10. 销毁解压对象,释放资源
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);

    return 0;
}

int main(int argc, char *argv[])
{
    if (argc != 4)
    {
        printf("Usage: %s <x0> <y0> <jpg_path>\n", argv[0]);
        printf("Example: %s 100 100 test.jpg\n", argv[0]);
        return -1;
    }

    // 初始化LCD
    if (lcd_init() < 0)
    {
        return -1;
    }

    // 转换坐标为整型
    int x0 = atoi(argv[1]);
    int y0 = atoi(argv[2]);
    // 显示JPEG图片到LCD
    jpeg_display_lcd(x0, y0, argv[3]);

    // 延时5秒,查看显示效果
    sleep(5);

    // 释放LCD
    lcd_uninit();
    return 0;
}
3.3.2 代码关键说明
  1. LCD 初始化:通过mmap映射 Linux 帧缓冲区/dev/fb0到用户空间,直接操作内存实现像素写入,是嵌入式 LCD 显示的通用方法;
  2. 像素格式转换:JPEG 解码后输出RGB888(每个分量 8 位),LCD 为 32 位 ARGB8888,通过rgb888_to_argb8888完成转换,可根据实际 LCD 屏修改格式(如 RGB565);
  3. 越界检查:在 LCD 指定位置显示时,检查图片右下角坐标是否超过 LCD 屏大小,防止数组越界;
  4. 行缓冲区JSAMPARRAY是 libjpeg 定义的行缓冲区类型,每次仅读取 1 行数据,节省内存(适合嵌入式小内存场景)。

3.4 编译代码

将工程目录结构整理为如下形式(include/lib/为移植后的 libjpeg 文件):

jpeg_project/
├── include/  # libjpeg头文件(jpeglib.h等)
├── lib/      # libjpeg库文件(libjpeg.a、libjpeg.so)
└── main.c    # 上述解码代码

执行交叉编译命令,使用-I指定头文件路径,-L指定库文件路径,-ljpeg链接 libjpeg 库:

# 交叉编译:arm-linux-gcc
arm-linux-gcc main.c -o jpeg_lcd -I./include -L./lib -ljpeg
# 若为x86主机测试,直接用gcc
gcc main.c -o jpeg_lcd -I./include -L./lib -ljpeg

3.5 运行程序

  1. x86 主机测试:注释掉 LCD 相关代码(lcd_init/lcd_uninit/rgb888_to_argb8888),仅打印 JPEG 图像信息,直接运行:
    ./jpeg_lcd 100 100 test.jpg
    
  2. ARM 开发板运行
    • 将编译后的可执行文件jpeg_lcd和 JPEG 图片test.jpg拷贝到开发板;
    • 将 libjpeg 动态库libjpeg.so.9拷贝到开发板/lib目录;
    • 执行运行命令:
      ./jpeg_lcd 100 100 test.jpg
      

    此时 LCD 屏将在 (100,100) 位置显示test.jpg图片,延时 5 秒后退出。

四、JPEG 图像压缩(编码)实战 ——BMP 转 JPEG

JPEG 压缩的核心是将无压缩的位图数据(如 BMP) 编码为 JPEG 压缩文件,可自定义压缩质量(0~100,数值越高质量越好,文件越大)。压缩流程与解压对称,同样需遵循固定步骤,核心是将 BMP 的 RGB 数据逐行写入 JPEG 编码对象。

4.1 BMP 文件基础

BMP 是无压缩的位图格式,文件结构分为文件头信息头像素数据三部分,像素数据为BGR888格式(与 JPEG 的 RGB888 相反),且像素数据按从下到上的顺序存储(需反转后编码)。本次代码处理24 位色深的 BMP 文件(最常用)。

4.2 编码核心流程

  1. 创建并初始化 JPEG 编码对象,关联错误处理对象;
  2. 以二进制方式创建 JPEG 文件,将文件指针绑定到编码对象;
  3. 设置编码参数(图像宽、高、颜色分量数、压缩质量);
  4. 开始编码,初始化编码内部状态;
  5. 读取 BMP 的 RGB 像素数据(转换 BGR 为 RGB,反转行顺序),逐行写入编码对象;
  6. 完成编码,刷新缓冲区,写入 JPEG 文件尾;
  7. 销毁 JPEG 编码对象,关闭文件,释放所有资源。

4.3 关键 API 说明

函数名 功能
jpeg_create_compress(&cinfo) 创建并初始化 JPEG 编码对象
jpeg_stdio_dest(&cinfo, outfile) 将文件指针绑定到编码对象,指定编码目标
jpeg_set_defaults(&cinfo) 设置默认编码参数
jpeg_set_quality(&cinfo, quality, TRUE) 设置压缩质量(0~100)
jpeg_start_compress(&cinfo, TRUE) 开始编码,初始化内部状态
jpeg_write_scanlines(&cinfo, buf, 1) 写入 1 行像素数据到编码对象
jpeg_finish_compress(&cinfo) 完成编码,写入文件尾
jpeg_destroy_compress(&cinfo) 销毁编码对象,释放所有资源

4.4 完整编码代码(BMP 转 JPEG)

本次代码实现24 位 BMP 文件转换为 JPEG 文件,支持自定义压缩质量,自动处理 BMP 的 BGR 转 RGB、行顺序反转问题。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jpeglib.h>
#include <jerror.h>

// BMP文件头(14字节)
typedef struct
{
    unsigned char bfType[2];    // BMP标识,必须为'BM'
    unsigned int   bfSize;      // 文件大小
    unsigned short bfReserved1; // 保留位
    unsigned short bfReserved2; // 保留位
    unsigned int   bfOffBits;   // 像素数据偏移地址
} BMPFileHeader;

// BMP信息头(40字节,24位BMP)
typedef struct
{
    unsigned int   biSize;      // 信息头大小
    int            biWidth;     // 图像宽度
    int            biHeight;    // 图像高度
    unsigned short biPlanes;    // 位面数,必须为1
    unsigned short biBitCount;  // 色深,24位为24
    unsigned int   biCompression; // 压缩方式,0为无压缩
    unsigned int   biSizeImage; // 像素数据大小
    int            biXPelsPerMeter; // 水平分辨率
    int            biYPelsPerMeter; // 垂直分辨率
    unsigned int   biClrUsed;   // 颜色表使用数
    unsigned int   biClrImportant; // 重要颜色数
} BMPInfoHeader;

// BMP转JPEG
// bmp_path:BMP文件路径;jpg_path:生成的JPEG文件路径;quality:压缩质量(0~100)
int bmp2jpeg(const char *bmp_path, const char *jpg_path, int quality)
{
    // 定义BMP相关变量
    BMPFileHeader bmp_file_hdr;
    BMPInfoHeader bmp_info_hdr;
    FILE *bmp_fp = NULL;
    FILE *jpg_fp = NULL;
    unsigned char *bmp_data = NULL;
    unsigned char *rgb_data = NULL;
    int row_stride = 0;  // BMP每行字节数(按4字节对齐)
    int rgb_stride = 0;  // JPEG每行字节数 = 宽度*3

    // 定义JPEG编码对象和错误处理对象
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    JSAMPARRAY buffer = NULL;

    // 检查压缩质量
    if (quality < 0 || quality > 100)
    {
        printf("Quality must be 0~100\n");
        return -1;
    }

    // 1. 打开BMP文件,读取文件头和信息头
    bmp_fp = fopen(bmp_path, "rb");
    if (bmp_fp == NULL)
    {
        perror("fopen bmp failed");
        return -1;
    }
    // 读取BMP文件头(14字节)
    fread(&bmp_file_hdr, sizeof(BMPFileHeader), 1, bmp_fp);
    // 检查是否为BMP文件
    if (bmp_file_hdr.bfType[0] != 'B' || bmp_file_hdr.bfType[1] != 'M')
    {
        printf("Not a valid BMP file\n");
        fclose(bmp_fp);
        return -1;
    }
    // 读取BMP信息头(40字节)
    fread(&bmp_info_hdr, sizeof(BMPInfoHeader), 1, bmp_fp);
    // 检查是否为24位BMP
    if (bmp_info_hdr.biBitCount != 24)
    {
        printf("Only support 24bit BMP file\n");
        fclose(bmp_fp);
        return -1;
    }
    // 检查是否为无压缩
    if (bmp_info_hdr.biCompression != 0)
    {
        printf("Only support uncompressed BMP file\n");
        fclose(bmp_fp);
        return -1;
    }

    printf("BMP image info: width=%d, height=%d, bitcount=%d\n",
           bmp_info_hdr.biWidth, bmp_info_hdr.biHeight, bmp_info_hdr.biBitCount);

    // 2. 读取BMP像素数据
    row_stride = (bmp_info_hdr.biWidth * 3 + 3) & ~3; // BMP每行4字节对齐
    rgb_stride = bmp_info_hdr.biWidth * 3;            // JPEG每行无对齐
    bmp_data = (unsigned char *)malloc(row_stride * bmp_info_hdr.biHeight);
    rgb_data = (unsigned char *)malloc(rgb_stride * bmp_info_hdr.biHeight);
    if (bmp_data == NULL || rgb_data == NULL)
    {
        perror("malloc failed");
        fclose(bmp_fp);
        return -1;
    }
    // 定位到像素数据起始位置
    fseek(bmp_fp, bmp_file_hdr.bfOffBits, SEEK_SET);
    fread(bmp_data, row_stride * bmp_info_hdr.biHeight, 1, bmp_fp);

    // 3. BMP数据处理:BGR转RGB + 行顺序反转(BMP从下到上,JPEG从上到下)
    for (int y = 0; y < bmp_info_hdr.biHeight; y++)
    {
        unsigned char *bmp_row = bmp_data + y * row_stride;
        unsigned char *rgb_row = rgb_data + (bmp_info_hdr.biHeight - 1 - y) * rgb_stride;
        for (int x = 0; x < bmp_info_hdr.biWidth; x++)
        {
            // BMP:BGR888 → JPEG:RGB888
            rgb_row[x*3] = bmp_row[x*3+2];     // R
            rgb_row[x*3+1] = bmp_row[x*3+1];   // G
            rgb_row[x*3+2] = bmp_row[x*3];     // B
        }
    }

    // 4. 初始化JPEG编码对象和错误处理对象
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    // 5. 打开JPEG文件,绑定到编码对象
    jpg_fp = fopen(jpg_path, "wb");
    if (jpg_fp == NULL)
    {
        perror("fopen jpg failed");
        fclose(bmp_fp);
        free(bmp_data);
        free(rgb_data);
        jpeg_destroy_compress(&cinfo);
        return -1;
    }
    jpeg_stdio_dest(&cinfo, jpg_fp);

    // 6. 设置编码参数
    cinfo.image_width = bmp_info_hdr.biWidth;    // 图像宽度
    cinfo.image_height = bmp_info_hdr.biHeight;  // 图像高度
    cinfo.input_components = 3;                  // 颜色分量数(RGB=3)
    cinfo.in_color_space = JCS_RGB;              // 输入颜色空间
    // 设置默认编码参数
    jpeg_set_defaults(&cinfo);
    // 设置压缩质量
    jpeg_set_quality(&cinfo, quality, TRUE);

    // 7. 开始编码
    jpeg_start_compress(&cinfo, TRUE);
    // 分配行缓冲区
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, rgb_stride, 1);

    // 8. 逐行写入RGB数据到编码对象
    while (cinfo.next_scanline < cinfo.image_height)
    {
        buffer[0] = rgb_data + cinfo.next_scanline * rgb_stride;
        jpeg_write_scanlines(&cinfo, buffer, 1);
    }

    // 9. 完成编码
    jpeg_finish_compress(&cinfo);
    // 10. 销毁编码对象,释放所有资源
    jpeg_destroy_compress(&cinfo);
    fclose(bmp_fp);
    fclose(jpg_fp);
    free(bmp_data);
    free(rgb_data);

    printf("BMP to JPEG success! %s -> %s (quality=%d)\n", bmp_path, jpg_path, quality);
    return 0;
}

int main(int argc, char *argv[])
{
    if (argc != 4)
    {
        printf("Usage: %s <bmp_path> <jpg_path> <quality>\n", argv[0]);
        printf("Example: %s test.bmp test.jpg 80\n", argv[0]);
        return -1;
    }

    // 转换压缩质量为整型
    int quality = atoi(argv[3]);
    // BMP转JPEG
    bmp2jpeg(argv[1], argv[2], quality);

    return 0;
}

4.5 代码关键说明

  1. BMP 文件解析:严格按照 BMP 文件结构读取文件头和信息头,校验文件标识、色深、压缩方式,确保为 24 位无压缩 BMP;
  2. 4 字节对齐处理:BMP 文件的像素数据每行按4 字节对齐,需通过row_stride = (width*3 +3) & ~3计算实际每行字节数,JPEG 无对齐要求;
  3. BGR 转 RGB:BMP 像素数据为 BGR888,JPEG 编码需要 RGB888,需逐像素交换 R 和 B 分量;
  4. 行顺序反转:BMP 像素数据按从下到上存储,JPEG 编码需要从上到下的顺序,需反转行数据后再编码;
  5. 压缩质量:通过jpeg_set_quality设置,TRUE 表示使用基准质量表,压缩质量 80 为兼顾质量和文件大小的最优值。

4.6 编译与运行

  1. 工程目录:与解码代码一致,包含include/lib/文件夹;
  2. 交叉编译命令
    arm-linux-gcc bmp2jpeg.c -o bmp2jpeg -I./include -L./lib -ljpeg
    
  3. 运行程序
    # 示例:将test.bmp转换为test.jpg,压缩质量80
    ./bmp2jpeg test.bmp test.jpg 80
    
    运行成功后生成test.jpg文件,可通过ls -l查看文件大小,验证压缩效果(24 位 BMP 转 JPEG 后文件体积会大幅减小)。

五、常见问题与解决方法

5.1 移植相关问题

  1. configure: error: cannot guess build type:未指定--host参数,或交叉编译工具链未配置环境变量;
  2. make: *** No rule to make target `all'. Stop:配置失败,未生成 Makefile,删除解压目录重新配置;
  3. 编译时提示头文件缺失./configure--prefix路径错误,或头文件未拷贝到工程目录。

5.2 编解码相关问题

  1. JPEG 解码提示文件格式错误:未以二进制方式打开文件(需用"rb"而非"r");
  2. LCD 显示图片花屏:像素格式转换错误(如 RGB888 转 RGB565 时位数计算错误),或 JPEG 行数据读取不完整;
  3. BMP 转 JPEG 后图片倒立:未处理 BMP 的行顺序反转,BMP 像素数据是从下到上存储的;
  4. 开发板运行提示找不到库文件:未将动态库libjpeg.so.9拷贝到开发板/lib目录,或库文件位数不匹配(x86 库不能在 ARM 运行)。

5.3 性能优化建议

  1. 内存优化:嵌入式系统内存较小,编解码时每次仅读取 / 写入 1 行数据,避免一次性加载整个图像;
  2. 压缩质量:根据实际需求设置压缩质量,如网络传输设为 50~70,本地显示设为 80~90;
  3. 格式适配:LCD 屏若为 RGB565 色深,解码后直接转换为 RGB565,避免 32 位转 24 位的内存浪费。

六、总结

本文详细讲解了libjpeg 库的 ARM-Linux 交叉编译移植JPEG 解压到 LCD 显示BMP 转 JPEG 压缩三大核心内容,所有步骤均附带图文和完整可运行代码,是嵌入式 Linux 图像处理的基础实战。

核心知识点回顾

  1. libjpeg 移植的核心是交叉编译配置,通过--prefix--host指定安装路径和目标平台;
  2. JPEG 解压 / 编码有固定流程,需牢记 API 的调用顺序,核心是操作jpeg_decompress_struct/jpeg_compress_struct对象;
  3. 嵌入式 LCD 显示的关键是帧缓冲区映射,直接操作内存实现高效像素写入;
  4. BMP 转 JPEG 需处理4 字节对齐BGR 转 RGB行顺序反转三个核心问题。

掌握 libjpeg 库的使用后,可拓展实现更复杂的图像处理功能,如 JPEG 图片缩放、灰度化、多图片拼接显示等,也可结合网络编程实现 JPEG 图片的网络传输与实时显示,为嵌入式视觉开发打下基础。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐