如何从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
相关产品推荐
相关产品推荐

