求无需OpenCV的C源码:合并3张JPEG Hex文件为QVGA图像
合并多幅同参数JPEG图像为单幅完整JPEG(无OpenCV依赖)
问题根源
直接拼接JPEG二进制文件或仅复用头文件会导致失真,因为JPEG是基于块的有损压缩格式:每幅JPEG都包含独立的帧定义、量化表和扫描标记。仅保留第一个头但直接拼接后续文件的二进制内容,会引入多余的JPEG标记(如重复的SOI、SOF、EOI),导致解码器解析错误。
正确实现思路
- 提取第一幅JPEG的完整文件头(从SOI
0xFFD8到第一个SOS0xFFDA之前的所有段,包括APPn、DQT、SOF0、DHT等) - 提取每幅JPEG的扫描数据段(从SOS
0xFFDA到EOI0xFFD9之前的所有数据,不包含EOI) - 将「头 + 所有扫描数据 + 最终EOI
0xFFD9」拼接成新的JPEG文件
C语言实现代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_SEGMENT_SIZE (1024 * 1024) // 1MB足够处理多数JPEG段 // 查找JPEG标记,跳过连续的0xFF填充 int find_jpeg_marker(unsigned char *buf, int buf_len, int pos, unsigned char marker) { while (pos < buf_len - 1) { if (buf[pos] == 0xFF) { int next_pos = pos + 1; // 跳过连续的0xFF while (next_pos < buf_len && buf[next_pos] == 0xFF) { next_pos++; } if (next_pos >= buf_len) break; if (buf[next_pos] == marker) { return next_pos; } pos = next_pos; } else { pos++; } } return -1; } // 读取JPEG文件的头和扫描数据 int split_jpeg(const char *filename, unsigned char **header, int *header_len, unsigned char **scan_data, int *scan_len) { FILE *fp = fopen(filename, "rb"); if (!fp) return -1; // 读取整个文件到内存 fseek(fp, 0, SEEK_END); int file_len = ftell(fp); fseek(fp, 0, SEEK_SET); unsigned char *file_buf = malloc(file_len); if (!file_buf) { fclose(fp); return -1; } fread(file_buf, 1, file_len, fp); fclose(fp); // 找SOI标记(0xFFD8) int soi_pos = find_jpeg_marker(file_buf, file_len, 0, 0xD8); if (soi_pos == -1) { free(file_buf); return -1; } // 找第一个SOS标记(0xFFDA) int sos_pos = find_jpeg_marker(file_buf, file_len, soi_pos + 2, 0xDA); if (sos_pos == -1) { free(file_buf); return -1; } // 提取头:从SOI到SOS的前一个字节 *header_len = sos_pos; *header = malloc(*header_len); memcpy(*header, file_buf, *header_len); // 找EOI标记(0xFFD9) int eoi_pos = find_jpeg_marker(file_buf, file_len, sos_pos + 2, 0xD9); if (eoi_pos == -1) { free(*header); free(file_buf); return -1; } // 提取扫描数据:从SOS到EOI的前一个字节 *scan_len = eoi_pos - sos_pos; *scan_data = malloc(*scan_len); memcpy(*scan_data, file_buf + sos_pos, *scan_len); free(file_buf); return 0; } // 合并多个JPEG文件 int merge_jpegs(const char **input_files, int file_count, const char *output_file) { if (file_count < 1) return -1; // 处理第一个文件,提取头 unsigned char *header = NULL; int header_len = 0; unsigned char *first_scan = NULL; int first_scan_len = 0; if (split_jpeg(input_files[0], &header, &header_len, &first_scan, &first_scan_len) != 0) { return -1; } // 打开输出文件 FILE *out_fp = fopen(output_file, "wb"); if (!out_fp) { free(header); free(first_scan); return -1; } // 写入头 fwrite(header, 1, header_len, out_fp); // 写入第一个文件的扫描数据 fwrite(first_scan, 1, first_scan_len, out_fp); free(header); free(first_scan); // 处理剩余文件,仅写入扫描数据 for (int i = 1; i < file_count; i++) { unsigned char *dummy_header = NULL; int dummy_header_len = 0; unsigned char *scan_data = NULL; int scan_len = 0; if (split_jpeg(input_files[i], &dummy_header, &dummy_header_len, &scan_data, &scan_len) != 0) { fclose(out_fp); return -1; } fwrite(scan_data, 1, scan_len, out_fp); free(dummy_header); free(scan_data); } // 写入最终EOI标记 unsigned char eoi[] = {0xFF, 0xD9}; fwrite(eoi, 1, sizeof(eoi), out_fp); fclose(out_fp); return 0; } int main() { // 示例:合并3个320×80的JPEG文件 const char *input_files[] = {"top.jpg", "middle.jpg", "bottom.jpg"}; int file_count = sizeof(input_files) / sizeof(input_files[0]); const char *output_file = "merged_qvga.jpg"; if (merge_jpegs(input_files, file_count, output_file) == 0) { printf("合并完成,输出文件:%s\n", output_file); } else { printf("合并失败\n"); } return 0; }
注意事项
- 所有输入JPEG必须参数完全一致:相同宽度(320)、色彩空间、采样率、量化表和DCT表。你的3幅320×80的图满足该条件,可正常合并。
- 若输入为Hex格式文件,需先转换为二进制文件(例如用
xxd -r input.hex input.jpg命令)。 - 代码未处理复杂异常(如文件损坏、格式不兼容),实际部署需添加错误检查逻辑。
内容的提问来源于stack exchange,提问作者sathya
相关产品推荐
相关产品推荐

