How to read multiple barcodes in one image with Barcode Tool

As you correctly described, the CVC Barcode tool out of the box is unable to cope with several barcodes or matrix codes in an image (or, to put it more precisely: it can cope, but it will only ever read one code and then finish).

However, there is in fact a way around this that does come at the cost of a little CPU time and a few lines of extra code. The approach is actually very blunt: Whenever a code was read, paint it over with some arbitrary color and continue reading until no more codes are found. With 2D codes this is fairly easy to implement as the barcode tool will return the corners of the code - exactly the information needed to paint over the code.

With 1D codes it becomes a bit more difficult because now the tool only reports the position of one line that spans all elements of the code, but the actual height of it is guesswork.

I’ve written up a short function that might help you. Disclaimer: I haven’t done too much testing on this - it’s Friday night after all - so the code might need some tweaking.

// some types we'll need...
struct barcoderesult_t
{
  std::string str;
  CVC_BC_INFO info;
};
typedef std::list<barcoderesult_t> barcoderesults_t;

// Now the actual implementation...
barcoderesults_t ReadMultiple(IMG img, CVC_BC_CONFIG cfg, int height1D = 20)
{
  barcoderesults_t retval;
  char str[256];
  CVC_BC_INFO info;
  // loop until no more codes are found
  while (CvcBcDecodeBarcode(img, 0, nullptr, CVC_BC_OMNI, 
    cfg, str, 256, &info, sizeof(info)) > 0)
  {
    barcoderesult_t res = { str, info };
    retval.push_back(res);
    // wipeout depends on symbology; 1D may be recognized by the
    // the content of p3 and p4 in the info structure
    bool is2DCode = ((info.p3_x != 0) && (info.p3_y != 0)
      && (info.p4_x != 0) && (info.p4_y != 0));
    TArea wipeout;
    if (is2DCode)
    {
      // for 2D codes we can use the result as-is
      wipeout.X0 = info.p1_x;
      wipeout.Y0 = info.p1_y;
      wipeout.X1 = info.p2_x;
      wipeout.Y1 = info.p2_y;
      wipeout.X2 = info.p4_x;
      wipeout.Y2 = info.p4_y;
    }
    else
    {
      // for 1D codes only one line will be reported - we'll have
      // to do some math on top of that...
      double dx = info.p2_x - info.p1_x;
      double dy = info.p2_y - info.p1_y;
      std::swap(dx, dy);
      dy *= -1.0;
      auto len = sqrt(dx * dx + dy * dy) * 2.0;
      dx /= len;
      dy /= len;
      wipeout.X0 = info.p1_x - dx * height1D;
      wipeout.Y0 = info.p1_y - dy * height1D;
      wipeout.X1 = info.p1_x + dx * height1D;
      wipeout.Y1 = info.p1_y + dy * height1D;
      wipeout.X2 = info.p2_x - dx * height1D;
      wipeout.Y2 = info.p2_y - dy * height1D;
    }
    InitializeImageArea(img, 0, wipeout, 128);
  }
  return retval;
}

… and, as you asked for C#, here goes…

namespace Barcode
{
  struct BarcodeResult
  {
    public string Str;
    public Cvb.Barcode.Info Info;
  }

  static class Utilities
  {
    public static List<BarcodeResult> ReadMultiple(Cvb.SharedImg img, Cvb.Barcode.CONFIG cfg, int height1D = 20)
    {
      var retval = new List<BarcodeResult>();
      // loop until no more codes are found
      Cvb.Barcode.Rect aoi = new Cvb.Barcode.Rect(0, 0, 
        Cvb.Image.ImageWidth(img) - 1, Cvb.Image.ImageHeight(img) - 1);
      string str;
      Cvb.Barcode.Info info;
      while (Cvb.Barcode.DecodeBarcode(img, 0, aoi, Cvb.Barcode.Direction.OMNI,
        cfg, out str, 256, out info) == Cvb.Barcode.DecodeCode.Success)
      {
        retval.Add(new BarcodeResult() { Str = str, Info = info });
        // wipeout depends on symbology
        bool is2DCode = ((info.p3_x != 0) && (info.p3_y != 0)
          && (info.p4_x != 0) && (info.p4_y != 0));
        Cvb.Image.TArea wipeout;
        if (is2DCode)
        {
          // for 2D codes we can use the result as-is
          wipeout.X0 = info.p1_x;
          wipeout.Y0 = info.p1_y;
          wipeout.X1 = info.p2_x;
          wipeout.Y1 = info.p2_y;
          wipeout.X2 = info.p4_x;
          wipeout.Y2 = info.p4_y;
        }
        else
        {
          // for 1D codes only one line will be reported - we'll have
          // to do some math on top of that...
          double dy = info.p2_x - info.p1_x;
          double dx = info.p2_y - info.p1_y;
          dy *= -1.0;
          var len = Math.Sqrt(dx * dx + dy * dy) * 2.0;
          dx /= len;
          dy /= len;
          wipeout.X0 = info.p1_x - dx * height1D;
          wipeout.Y0 = info.p1_y - dy * height1D;
          wipeout.X1 = info.p1_x + dx * height1D;
          wipeout.Y1 = info.p1_y + dy * height1D;
          wipeout.X2 = info.p2_x - dx * height1D;
          wipeout.Y2 = info.p2_y - dy * height1D;
        }
        Cvb.Image.InitializeImageArea(img, 0, wipeout, 128);
      }
      return retval;
    }

  }
}

I have opted for making the 1D code height a parameter (which will of course need to be set to a value that fits the image content). Another alternative is to work with a fixed height of e.g. 10 lines and keep reading and if duplicate results come up discard the duplicates rather than pushing them to the result list.

The extra cost in processing time should not be too drastic. After all the read operation always consists of two steps: Finding the code and decoding it. The decoding for N codes will have to be done N times anyway, so the only overhead is scanning the image multiple times for code-like-structures (but that’s the faster step of the two steps) and painting over the decoded codes.

It should be pointed out that this approach might be tricky with Pharmacodes as these have no consistency check features (see also here). As a consequence of that, the tool might encounter many false positive Pharmacodes if the read codes have not been wiped out completely).

1 Like