嵌入式Linux实战:JPEG编解码与LCD显示
在嵌入式 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.h、jerror.h、jconfig.h、jmorecfg.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 移植准备
- 下载源码:从 IJG 官网下载最新版源码包,地址:https://www.ijg.org/,本次使用
jpegsrc.v9f.tar.gz; - 交叉编译工具链:已安装 ARM-Linux 交叉编译工具链(如
arm-linux-gcc),并配置好环境变量; - 主机环境:Ubuntu 系统安装
make、gcc、tar等基础工具(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 解码核心流程(必须背会)
- 创建并初始化 JPEG 解压对象,关联错误处理对象;
- 以二进制方式打开 JPEG 文件,将文件指针绑定到解压对象;
- 读取 JPEG 文件头,获取图像宽、高、色深等信息;
- 设置解压参数(可选,默认参数即可满足大部分需求);
- 开始解压,初始化解压内部状态;
- 循环读取每行的 RGB 像素数据,写入 LCD 屏;
- 完成解压,释放解压过程的临时内存;
- 销毁 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 代码关键说明
- LCD 初始化:通过
mmap映射 Linux 帧缓冲区/dev/fb0到用户空间,直接操作内存实现像素写入,是嵌入式 LCD 显示的通用方法; - 像素格式转换:JPEG 解码后输出RGB888(每个分量 8 位),LCD 为 32 位 ARGB8888,通过
rgb888_to_argb8888完成转换,可根据实际 LCD 屏修改格式(如 RGB565); - 越界检查:在 LCD 指定位置显示时,检查图片右下角坐标是否超过 LCD 屏大小,防止数组越界;
- 行缓冲区:
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 运行程序
- x86 主机测试:注释掉 LCD 相关代码(
lcd_init/lcd_uninit/rgb888_to_argb8888),仅打印 JPEG 图像信息,直接运行:./jpeg_lcd 100 100 test.jpg - 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 编码核心流程
- 创建并初始化 JPEG 编码对象,关联错误处理对象;
- 以二进制方式创建 JPEG 文件,将文件指针绑定到编码对象;
- 设置编码参数(图像宽、高、颜色分量数、压缩质量);
- 开始编码,初始化编码内部状态;
- 读取 BMP 的 RGB 像素数据(转换 BGR 为 RGB,反转行顺序),逐行写入编码对象;
- 完成编码,刷新缓冲区,写入 JPEG 文件尾;
- 销毁 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 代码关键说明
- BMP 文件解析:严格按照 BMP 文件结构读取文件头和信息头,校验文件标识、色深、压缩方式,确保为 24 位无压缩 BMP;
- 4 字节对齐处理:BMP 文件的像素数据每行按4 字节对齐,需通过
row_stride = (width*3 +3) & ~3计算实际每行字节数,JPEG 无对齐要求; - BGR 转 RGB:BMP 像素数据为 BGR888,JPEG 编码需要 RGB888,需逐像素交换 R 和 B 分量;
- 行顺序反转:BMP 像素数据按从下到上存储,JPEG 编码需要从上到下的顺序,需反转行数据后再编码;
- 压缩质量:通过
jpeg_set_quality设置,TRUE 表示使用基准质量表,压缩质量 80 为兼顾质量和文件大小的最优值。
4.6 编译与运行
- 工程目录:与解码代码一致,包含
include/和lib/文件夹; - 交叉编译命令:
arm-linux-gcc bmp2jpeg.c -o bmp2jpeg -I./include -L./lib -ljpeg - 运行程序:
运行成功后生成# 示例:将test.bmp转换为test.jpg,压缩质量80 ./bmp2jpeg test.bmp test.jpg 80test.jpg文件,可通过ls -l查看文件大小,验证压缩效果(24 位 BMP 转 JPEG 后文件体积会大幅减小)。
五、常见问题与解决方法
5.1 移植相关问题
- configure: error: cannot guess build type:未指定
--host参数,或交叉编译工具链未配置环境变量; - make: *** No rule to make target `all'. Stop:配置失败,未生成 Makefile,删除解压目录重新配置;
- 编译时提示头文件缺失:
./configure时--prefix路径错误,或头文件未拷贝到工程目录。
5.2 编解码相关问题
- JPEG 解码提示文件格式错误:未以二进制方式打开文件(需用
"rb"而非"r"); - LCD 显示图片花屏:像素格式转换错误(如 RGB888 转 RGB565 时位数计算错误),或 JPEG 行数据读取不完整;
- BMP 转 JPEG 后图片倒立:未处理 BMP 的行顺序反转,BMP 像素数据是从下到上存储的;
- 开发板运行提示找不到库文件:未将动态库
libjpeg.so.9拷贝到开发板/lib目录,或库文件位数不匹配(x86 库不能在 ARM 运行)。
5.3 性能优化建议
- 内存优化:嵌入式系统内存较小,编解码时每次仅读取 / 写入 1 行数据,避免一次性加载整个图像;
- 压缩质量:根据实际需求设置压缩质量,如网络传输设为 50~70,本地显示设为 80~90;
- 格式适配:LCD 屏若为 RGB565 色深,解码后直接转换为 RGB565,避免 32 位转 24 位的内存浪费。
六、总结
本文详细讲解了libjpeg 库的 ARM-Linux 交叉编译移植、JPEG 解压到 LCD 显示、BMP 转 JPEG 压缩三大核心内容,所有步骤均附带图文和完整可运行代码,是嵌入式 Linux 图像处理的基础实战。
核心知识点回顾
- libjpeg 移植的核心是交叉编译配置,通过
--prefix和--host指定安装路径和目标平台; - JPEG 解压 / 编码有固定流程,需牢记 API 的调用顺序,核心是操作
jpeg_decompress_struct/jpeg_compress_struct对象; - 嵌入式 LCD 显示的关键是帧缓冲区映射,直接操作内存实现高效像素写入;
- BMP 转 JPEG 需处理4 字节对齐、BGR 转 RGB、行顺序反转三个核心问题。
掌握 libjpeg 库的使用后,可拓展实现更复杂的图像处理功能,如 JPEG 图片缩放、灰度化、多图片拼接显示等,也可结合网络编程实现 JPEG 图片的网络传输与实时显示,为嵌入式视觉开发打下基础。
更多推荐
所有评论(0)