Fast Barcode 39 Detection






4.71/5 (8 votes)
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.
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.
// 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.
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.
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.
// 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.
// 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.)
// 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?
// 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
:
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