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
{
[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.
for(int o=0;o<2;o++)
{
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);
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();
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.
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;
}
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.
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);
for(vector<Transition>::iterator it = transitions->begin(); it != transitions->end(); ++it)
{
if ( it->size == val )
{
it->freq++;
find = true;
break;
}
}
if ( !find ) {
Transition t;
t.freq = 1;
t.size = val ;
transitions->push_back(t);
}
}
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.)
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;
}
if (( r >= 3.2 ) && ( r <= 1.8 )) return std::string();
int delta = min / 2;
if ( delta > 10 ) delta = 10;
Decoding, have we found a barcode?
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 {
if (( read >= min - delta ) && ( read <= min + delta ))
pos++;
else if (( read >= max - delta ) && ( read <= max + delta ))
val += ( 1<< (8-pos++));
else
{
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