You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求无需OpenCV的C源码:合并3张JPEG Hex文件为QVGA图像

合并多幅同参数JPEG图像为单幅完整JPEG(无OpenCV依赖)

问题根源

直接拼接JPEG二进制文件或仅复用头文件会导致失真,因为JPEG是基于块的有损压缩格式:每幅JPEG都包含独立的帧定义、量化表和扫描标记。仅保留第一个头但直接拼接后续文件的二进制内容,会引入多余的JPEG标记(如重复的SOI、SOF、EOI),导致解码器解析错误。

正确实现思路

  1. 提取第一幅JPEG的完整文件头(从SOI 0xFFD8 到第一个SOS 0xFFDA 之前的所有段,包括APPn、DQT、SOF0、DHT等)
  2. 提取每幅JPEG的扫描数据段(从SOS 0xFFDA 到EOI 0xFFD9 之前的所有数据,不包含EOI)
  3. 将「头 + 所有扫描数据 + 最终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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 12:05:29