OpenCV - UPC Barcode Reader Part 3

After explaining how the UPC system works in Part 1, I have shown how to decode a digit from an image in Part 2 of this serie. In this article I will now demonstrate how to read the entire barcode.

Helper functions

We will need other helper functions for this last task.

We need a function to skip the quiet zone, skip_value advances the scan pointer to the guard pattern

void skip_quiet_zone(const Mat& img, cv::Point& cur)
{
  while (img(cur) == SPACE)
    ++cur.x;
}

We need a function to read the left guard and return the smallest width.

unsigned read_lguard(const Mat& img, cv::Point& cur)
{
  int widths[3] = { 0, 0, 0 };
  int pattern[3] = { BAR, SPACE, BAR };
  for (int i = 0; i < 3; ++i)
    while (img(cur) == pattern[i])
    {
      ++cur.x;
      ++widths[i];
    }
  return widths[0];
}

Here I only return the first width but the best would be to compute the mean. Even better would be to scan the right guard and compute the mean/median of all values. This would be much better for noisy images.

We also need a function to skip the middle guard, note that we don't care about the width right now.

void skip_mguard(const Mat& img, cv::Point& cur)
{
  int pattern[5] = { SPACE, BAR, SPACE, BAR, SPACE };
  for (int i = 0; i < 5; ++i)
    while (img(cur) == pattern[i])
      ++cur.x;
}

Reading the barcode

We have everything we need, the rest is simple: we load the image, we invert it, threshold it, we compute the smallest width with the left guard, we read the 6 left digits, the middle guard, the 6 right digits and that's all ! At the end we print the decoded sequence.

void read_barcode(const std::string& filename)
{
  Mat img = cv::imread(filename, 0);
  cv::Size size = img.size();
  cv::Point cur(0, size.height / 2);
 
  cv::bitwise_not(img, img);
  cv::threshold(img, img, 128, 255, cv::THRESH_BINARY);
  skip_quiet_zone(img, cur);
 
  pattern_map table;
  setup_map(table); // presented in part 2.
 
  int unit_width = read_lguard(img, cur);
  display(img, cur);
 
  std::vector<int> digits;
  for (int i = 0; i < 6; ++i)
  {
    int d = read_digit(img, cur, unit_width, table, LEFT);
    digits.push_back(d);
  }
 
  skip_mguard(img, cur);
 
  for (int i = 0; i < 6; ++i)
  {
    int d = read_digit(img, cur, unit_width, table, RIGHT);
    digits.push_back(d);
  }
 
  for (int i = 0; i < 12; ++i)
    std::cout << digits[i];
  std::cout << std::endl;
  cv::waitKey();
}

Going further

This reader is simple and far from perfect. There are numerous ways to improve it:
- Use several scan lines. For each bit, compute the median of the intensity found by each scan line.
- We assume we always have a match in our decoding table, we should rather compute the distance to each binary sequence and we should choose the digit with the lowest distance. If we have no digit with a low distance, we can try several digits and see which one satisfies the check digit equation.
- ...
But without doubts, the toughest problem with barcode reading will be concerning preprocessing. The input can be noisy, blurry, skewed, scratched...

Comments are closed.