FFOMR
bidimensional Optical Mark Recognition

Prototype

Put the following prototype in the prototypes section of the application program header file:

int ( __stdcall *pfnFFOMR )( int, int, int, int, int, int, int, int, int, int, int, void*, char* );

Purpose

Automatic data input from any kind of printed forms.

Description and use

I wrote my first Optical Mark Recognition routine in 1991, to get automated data entry for an optical data storage and retrieval system that I wrote for the italian nation-wide electrical company (read the story and see the example
here). The OMR that I am presenting here takes its origins from that old project.

At the end of the 90s' I ported the routine from the original 16-bit DOS to the more modern 32-bit Win32. But the original recognition approach, and some of the original assembly routines, has been left unchanged. The OMR that I am presenting you here has the following outstanding characteristics:

  • extreme reliability: once you set up all the required parameters it works for years without any problem
  • lightning fast: you can recognize as many marks as you want, thousands if you want, in a fraction of a second
  • two-dimensional recognition: the marks are recognized across the whole sheet, and not just across a single line (like the original project did)
  • not sensitive to moderate rotations of the image: in other words the recognition is not sensitive to a moderate skew of the sheet passing through the Automatic Document Feeder of the scanner

Specification

The sheet should be A4-size (or letter-size) and should have been scanned at 200 dpi, one bit per pixel. The image of the sheet should be accomodated in memory, and should be represented in black & white at one byte per pixel (0x00=black pixel, 0xFF=white pixel).

The sheet must present 4 positioning marks close to the corners. Positioning marks diameter should be 3 to 4 millimeters (you can use 4 asterisks if you want, look at the first given example) and their position is not critical, provided they stand exactly at the four corners of a rectangle. The four positioning marks define the recognition area inside of which any mark will be searched for.

All the potential marks should stand at the intersection of a number of rows and columns (a "matrix" for short). The matrix has a number of rows and columns, a width, a height, and its position within the recognition area is defined by its displacement from the top left positioning mark.

Please refer to this sketch to visualize a 18 by 3 example matrix that will be used in the first example in the examples section below.

As you can see it takes 6 parameters to univocally define the matrix (parameters 4, 5, 6, 7, 8 and 9), and you can easily figure out them all looking at the given example sketch.

You can define more than one matrix in the same form, and call the routine several consecutive times to recognize the marks one matrix at a time.

Recognized marks are returned in the ASCII string pointed to by the 13th parameter. An ASCII 0 (0x30) means no mark, and the ASCII 1 (0x31) means mark found. This implies that the ASCII string pointed to by the 13th parameter must be allocated to accomodate one byte for each potential mark in the matrix, or, in other words, the string should be as long as the product of the 4th parameter by the 5th parameter (plus one final byte for the final ASCIIZ null).

A number of xls example forms are given to help you figure out some useful applications (see below).

Originally I used an ASCII editor to create and print my forms. Now you can use Excel or any other tool you want to get a more pleasant form style.

Parameters
  • int image width in pixels/8 (example: the image width is 1664 pixels, and you should pass 1664/8; also remember that the image width should be an exact multiple of 8, because the division by 8 must not give any rest)
  • int image length in lines
  • int mode:
    • 0: normal recognition
    • 1: normal recognition and display all recognized marks
    • 2: normal recognition and display all the possible marks positions
    (note: "display" means that a cross is automatically printed on the image into the memory, and if you refresh the image you will see those crosses; use mode=2 to display the whole matrix and to check that it exactly masks the places where the marks must be recognized; use mode=1 to visually display only the recognized marks -it's amazing!-)
  • int number of rows in the matrix
  • int number of columns in the matrix
  • int horizontal offset of the top left place in the matrix, in pixels from the top left positioning mark
  • int vertical offset of the top left place in the matrix, in pixels from the top left positioning mark
  • int matrix width in pixels
  • int matrix height in pixels
  • int threeshold (usually 6, it is the max number of grouped pixels that can't be considered a "positioning mark"; if you reduce it, every little stain on the sheet will be recognized as a positioning mark; if you enlarge it too much, no positioning marks will be recognized)
  • int sensitivity (usually 20, it is the minimum number of grouped pixels that can be considered a mark; if you set it to 3 a little stain in the place where the mark is expected will be recognized as if the stain were the mark; if you set it to 100 only giant marks will be recognized)
  • void* pointer to the image in memory
  • char* pointer to the allocated null terminated string that will contain the result

Return value

Remember that the function returns the recognized marks into the string pointed to by the 13th parameter. Look at the first example below to get more information about the recognized marks. This stated, the function returns an integer with the following meaning:
  • 0: no error
  • 1: top left positioning mark not found
  • 2: top right positioning mark not found
  • 3: bottom left positioning mark not found
  • 4: bottom right positioning mark not found
  • 5: vertical distance difference between top and bottom positioning marks
  • 6: horizontal distance difference between left and right positioning marks
(Errors 5 and 6 mean that the four positioning marks are *not* exactly at the corners of a rectangle).

Application examples

See also
  • FFTiffServices: single image TIFF file management
  • FFScan: SCSI scanner single page scan
  • FFDec: CCITT group 4 bidimensional decompression
  • FFView: 256 colours *full screen* bitmap display with pan, zoom, rotation, etc.

Notes

This function is lightning fast and reliable. Use it for automated data entry from any printed checkbox form. It requires a bit of patience to set up the parameters, but once the setup is completed it will work for years without troubles. Avoid staples, finger prints, hand writing, and stains in the surroundings of the four positioning marks, because these would make the function fail. Use Excel (or similar spreadsheet like Calc in OpenOffice) to generate your form. A good base to start from are the zipped
examples that come from one of the real application that I have developed.