Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Fast Barcode 39 Detection

4.71/5 (8 votes)
11 Sep 2014CPOL2 min read 17.6K   1.3K  
Fast detection of big barcode 39

Introduction

You can detect barcode39 from image. Detect it as fast as possible, because it's the first step of the process, barcode is used as document separator in image flow.

Background

The specification are available at Wikipedia Code 39.

Using external library FreeImage 3.16.0.

Using the Code

The main application is written in C#. It's used to reorder the pages of document before its creation in the automatic document recognition process.

So this library will be declared in C#, and called from this environment. The step parameter is the height step which is used to parse image.

You need to update the project with your FreeImage library path in order to compile it. Don't forget to copy the cb93.dll to the Host binary output folder.

class Sample
{
     // Declaration 
   [DllImport("Cb39.dll")]
   [return: MarshalAs(UnmanagedType.SafeArray)]
   private extern static string[] GetCodeBarreList([MarshalAs(UnmanagedType.LPStr)]  string file , [MarshalAs(UnmanagedType.U4)] int step);
   
   static void Main(string[] args)
   {
            DateTime t2 = DateTime.Now;
            string[] p2 = GetCodeBarreList(@"C:\TEMP\TEST.TIF",20);
            DateTime t3 = DateTime.Now;
            var ts2 = new TimeSpan(t3.Ticks - t2.Ticks);
            Console.WriteLine("{1} Delay {0} ms", ts2.TotalMilliseconds, string.Join(" - ", p2));

            Console.ReadKey();
    }
}

How the Application Works

First step, it loads the image with FreeImage lib, and convert to greyscale.

C++
FreeImage_Initialise(false);
FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(file->c_str());
FIBITMAP * fimg = FreeImage_Load(fif, file->c_str() ,0);
FIBITMAP * cfimg = FreeImage_ConvertToGreyscale(fimg);
FIBITMAP * img , * rcfimg ;

Second step, there is two pass to detect barcode depending on its orientation. For optimization, you can remove the rotation of the image if you know the orientation of the barcode.

C++
// try to detect vertical and horizontal barcode
for(int o=0;o<2;o++)
{
    // Rotation
    if ( o == 1 ) {
        rcfimg = FreeImage_Rotate(cfimg,90);
        img = rcfimg;
    } else {
        img = cfimg;
    }
...
}

Third step, parse image line by line width step, and get barcode string as result. Check and append to the list.

C++
unsigned int width  = FreeImage_GetWidth(img);
  unsigned int height = FreeImage_GetHeight(img);
  if ( step == 0 ) step = height ;
  for(unsigned int y = 0; y < height ; y += step )
  {
      BYTE * pScanLine = FreeImage_GetScanLine(img, y );
      std::string code = GetCode39(pScanLine, width);

      // check and append to list
      if ( code.length() > 2 )
      {
          if ((  code.compare(0,1,"*") == 0 ) && (  code.compare( code.length() - 1,1,"*") == 0 ))
          {
              bool find = false;
              std::vector<std::string>::iterator it = result->begin();
              while (( it != result->end()) && (!find ))
              {
                  std::string val = *it;
                  find = ( val.compare(code) == 0 );
                  ++it;
              }
              if (!find) result->push_back(code);
          }
      }
  }

Last step, free and convert to a useable C# pointer.

C++
if ( rcfimg != NULL )
{
    FreeImage_Unload(rcfimg);
    rcfimg = NULL;
}
if ( cfimg != NULL )
{
    FreeImage_Unload(cfimg);
    cfimg = NULL;
}
if ( fimg != NULL)
{
    FreeImage_Unload(fimg);
    fimg = NULL;
}
FreeImage_DeInitialise();

// Convert to SafeArray
SAFEARRAY * codesBarre = ConvertStringPtrVectorToSafeArray(result);

return codesBarre;

The GetCode39 function parse line, point by point. It compares the previous color to the current color, on difference it stores the segment size of the constant color.

C++
// Detect black/white, white/black transition
unsigned int size = 0;
vector<unsigned int> * seg = new vector<unsigned int>();
BYTE previous_color = (BYTE) (*pScanLine++);
for(unsigned int x=1;x < width ; x++ )
{
    BYTE current_color  = (BYTE) (*pScanLine++);
    if (((  previous_color > 196 ) && ( current_color < 64  )) ||
        (( previous_color < 64  ) && ( current_color > 196 )) )
    {
            seg->push_back(++size);
            size = 0;
    } else
        size++;

    previous_color = current_color;
}

// Segment count
unsigned int tcount =  seg->size();
if ( tcount == 0 ) return std::string();

We need to know which is the most used segment's size. We use the Transition structure to store the segment size and the frequency.

C++
// count the frequency of segments size
vector<Transition> * transitions = new vector<Transition>();
for (unsigned int i = 0 ; i < tcount ; i++)
{
    bool find = false;
    unsigned int val = (unsigned int) seg->at(i);
    // Rechercher
    for(vector<Transition>::iterator it = transitions->begin(); it != transitions->end(); ++it)
    {
        if ( it->size == val )
        {
            it->freq++;
            find = true;
            break;
        }
    }

    // Append if not exist
    if ( !find ) {
        Transition t;
        t.freq = 1;
        t.size = val ;
        transitions->push_back(t);
    }
}

// Order by frequency
sort(transitions->begin(), transitions->end(), SortTransitionsByFrequency );

In order to decode barcode, we have to find the large bar value and the fine one. We also compute an acceptable range for the segment size as the half size of the fine bar. (Limited to a hardcode value of 10 pixels.)

C++
  // try to guess fine and large bar
  unsigned int max = 0;
  unsigned int min = width;
  double r ;
  for(vector<Transition>::iterator it = transitions->begin(); it != transitions->end(); ++it)
  {
      if ( it->size > max ) max = it->size;
      if ( it->size < min ) min = it->size;

      r = 0.0f;
      if ( min != 0 )  r = max*1.0f / min*1.0f;
      if (( r < 3.2 ) && ( r > 1.8 )) break;
  }

  // Sanity check
  if (( r >= 3.2 ) && ( r <= 1.8 )) return std::string();

// Acceptable range
  int delta = min / 2;
  if ( delta > 10 ) delta = 10;

Decoding, have we found a barcode?

C++
// Decoding
std::string code = std::string();
unsigned int val = 0;
unsigned int pos = 0;
BOOL skip = false;
for(unsigned int i = 0 ; i< tcount ; i++)
{
    unsigned int read = (unsigned int) seg->at(i);
    if ( skip )
        skip = false ;
    else {
  // fine bar
        if (( read >= min - delta ) && ( read <= min + delta ))
            pos++;
    // large
        else if (( read >= max - delta ) && ( read <= max + delta ))
          val += ( 1<< (8-pos++));
        else
        {
          // reset
          val = 0;
          pos = 0;
        }

        if ( pos == 9 ) {
            code.append(Decode(val));
            skip = true;
            val = 0;
            pos = 0;
        }
    }

}

return code;

The decode function translates byte to string:

C++
    string Decode(unsigned int code)
    {
    std::string result = std::string();
    switch(code)
    {
        
        case 265 : 
            result.assign("A");
            break;
        ....
        
        default:
            break;
    }

    return result;
}

History

  • 06-2014 First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)