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

如何从BMP文件识别12位UPC条码?已有读取代码求后续指导

从BMP文件提取12位条码的完整指南

你已经成功把BMP文件读成了灰度图,这可是非常关键的第一步!接下来咱们就一步步把条码数字(比如你给的示例081034489030)提取出来,整个流程分为图像预处理、条码定位、条纹提取、解码四个核心步骤,结合你的现有代码来实现:

第一步:图像二值化

你的代码已经把RGB图像转成了单通道灰度值(取了蓝色通道),但条码是黑白分明的,咱们需要把灰度图转成纯黑白的二值图,这样后续识别条纹会更准确。设定一个阈值(比如中间值127),把灰度值大于阈值的设为白色(255),小于的设为黑色(0):

void part1() {
    int width, height;
    unsigned char ** img = NULL;
    img = readBMP("package.bmp", &height, &width);

    // 新增:二值化处理
    unsigned char** binary_img = (unsigned char**)malloc(width * sizeof(unsigned char*));
    for (int i = 0; i < width; i++) {
        binary_img[i] = (unsigned char*)malloc(height * sizeof(unsigned char));
        for (int j = 0; j < height; j++) {
            // 可根据实际图像调整阈值,这里用127作为中间值
            binary_img[i][j] = (img[i][j] > 127) ? 255 : 0;
        }
    }

    // 后续步骤代码写在这里...
}

第二步:定位条码所在行

条码是垂直排列的黑白条纹,咱们可以扫描每一行,计算该行的颜色跳变次数——跳变次数最多的行就是条码的核心区域(因为条纹密集,跳变多):

// 新增:寻找条码所在的目标行
int findBarcodeRow(unsigned char** img, int width, int height) {
    int max_changes = 0;
    int target_row = 0;
    for (int y = 0; y < height; y++) {
        int changes = 0;
        unsigned char prev = img[0][y];
        for (int x = 0; x < width; x++) {
            if (img[x][y] != prev) {
                changes++;
                prev = img[x][y];
            }
        }
        if (changes > max_changes) {
            max_changes = changes;
            target_row = y;
        }
    }
    return target_row;
}

在part1里调用这个函数:

int barcode_row = findBarcodeRow(binary_img, width, height);

第三步:提取条码的条纹宽度序列

找到目标行后,咱们扫描这一行的像素,记录每个连续黑/白块的宽度,生成一个条纹段的列表:

// 新增:定义条纹段结构体
typedef struct {
    int width;
    unsigned char color; // 0=黑色,255=白色
} BarSegment;

// 新增:提取目标行的条纹段
BarSegment* extractSegments(unsigned char** img, int width, int row, int* segment_count) {
    BarSegment* segments = (BarSegment*)malloc(width * sizeof(BarSegment));
    int count = 0;
    int current_width = 1;
    unsigned char current_color = img[0][row];
    
    for (int x = 1; x < width; x++) {
        if (img[x][row] == current_color) {
            current_width++;
        } else {
            segments[count].width = current_width;
            segments[count].color = current_color;
            count++;
            current_color = img[x][row];
            current_width = 1;
        }
    }
    // 加入最后一个条纹段
    segments[count].width = current_width;
    segments[count].color = current_color;
    count++;
    
    *segment_count = count;
    return segments;
}

在part1里调用:

int seg_count;
BarSegment* segments = extractSegments(binary_img, width, barcode_row, &seg_count);

第四步:解码条纹为数字

你给出的12位条码符合UPC-A码的结构:起始符(3个窄条:黑-白-黑)→ 6个左侧数字 → 中间分隔符(5个窄条:白-黑-白-黑-白)→ 6个右侧数字 → 终止符(黑-白-黑)。

首先咱们需要把条纹宽度归一化(因为图像可能有缩放,把所有宽度除以最小条纹宽度,得到相对宽度):

// 新增:获取最小条纹宽度
int getMinSegmentWidth(BarSegment* segments, int count) {
    int min = segments[0].width;
    for (int i = 1; i < count; i++) {
        if (segments[i].width < min) {
            min = segments[i].width;
        }
    }
    return min;
}

然后归一化:

int min_width = getMinSegmentWidth(segments, seg_count);
// 归一化每个条纹的宽度,四舍五入取整
for (int i = 0; i < seg_count; i++) {
    segments[i].width = round((float)segments[i].width / min_width);
}

接下来根据UPC-A的编码规则匹配数字,左侧数字用A编码,右侧用C编码(和A编码黑白反转):

// 新增:匹配左侧A编码的数字
int matchLeftANumber(int* pattern) {
    // UPC-A左侧A编码表,每个数字对应4个条纹宽度
    int codes[10][4] = {
        {3,2,1,1}, {2,2,2,1}, {2,1,2,2}, {1,4,1,1}, {1,1,3,2},
        {1,2,3,1}, {1,1,1,4}, {1,3,1,2}, {1,2,1,3}, {3,1,1,2}
    };
    for (int i = 0; i < 10; i++) {
        if (pattern[0] == codes[i][0] && pattern[1] == codes[i][1] &&
            pattern[2] == codes[i][2] && pattern[3] == codes[i][3]) {
            return i;
        }
    }
    return -1; // 匹配失败
}

// 新增:匹配右侧C编码的数字
int matchRightNumber(int* pattern) {
    // UPC-A右侧C编码表(和左侧A编码黑白反转)
    int codes[10][4] = {
        {1,1,2,3}, {1,2,2,2}, {2,2,1,2}, {1,1,4,1}, {2,3,1,1},
        {1,3,2,1}, {4,1,1,1}, {2,1,3,1}, {3,1,2,1}, {2,1,1,3}
    };
    for (int i = 0; i < 10; i++) {
        if (pattern[0] == codes[i][0] && pattern[1] == codes[i][1] &&
            pattern[2] == codes[i][2] && pattern[3] == codes[i][3]) {
            return i;
        }
    }
    return -1; // 匹配失败
}

最后解析整个条纹序列,提取12位数字:

// 新增:解析条码数字
char barcode[13] = {0}; // 存储12位数字+结束符
int idx = 0;
int seg_idx = 3; // 跳过起始符(前3个条纹段)

// 解析左侧6个数字
for (int i = 0; i < 6; i++) {
    int pattern[4];
    for (int j = 0; j < 4; j++) {
        pattern[j] = segments[seg_idx + j].width;
    }
    int num = matchLeftANumber(pattern);
    barcode[idx++] = '0' + num;
    seg_idx += 4;
}

seg_idx += 5; // 跳过中间分隔符(5个条纹段)

// 解析右侧6个数字
for (int i = 0; i < 6; i++) {
    int pattern[4];
    for (int j = 0; j < 4; j++) {
        pattern[j] = segments[seg_idx + j].width;
    }
    int num = matchRightNumber(pattern);
    barcode[idx++] = '0' + num;
    seg_idx += 4;
}

// 输出结果
printf("提取的条码:%s\n", barcode);

最后别忘了释放内存

避免内存泄漏,记得释放所有申请的内存:

// 释放内存
for (int i = 0; i < width; i++) {
    free(img[i]);
    free(binary_img[i]);
}
free(img);
free(binary_img);
free(segments);

内容的提问来源于stack exchange,提问作者raf_OH

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:49:54