Scanning Documents Written in Blue Ink – biscan

After writing the post on converting PNMs to DjVu, I’ve run into some trouble scanning documents written in blue ink. The problem: XSane didn’t allow me to set the threshold for converting the scanned image to line-art (B&W). So, I tried scanning the document in grayscale and in color and converting it afterward to bitonal using ImageMagick. This ended up with two results. When I used the -monochrome command-line switch, the conversion looked good, but it used halftones (dithering). When I tried to convert it to DjVu, it resulted in a document size twice as large as normal B&W would. The other thing that I tried was using the -threshold switch. The DjVu-compressed document size was much better now, but the document was awful-looking; either it was too dark, or some of the text disappeared. After giving it some thought, I knew I could find a better solution.

I came up with the idea that I needed to find a way to identify the text written in blue ink and make sure it turned black. The model I used was to identify it using the blue channel. So I tried applying a dynamic threshold that would give more weight to the blue channel. I’ve used the gamma option in ImageMagick, but it didn’t turn out well enough. I had to take different actions depending on the blue level.

My next shot at this was to use ImageMagick’s fx operator. It allows you to iterate over the pixels in the image and apply some custom expression. While it sounded very good, it turned out to be a disaster, as this thing was very slow. It is so slow that it becomes useless on high-resolution pictures; it didn’t finish operating on the 300dpi scanned document even an hour after it started. In my opinion, it is below par even when operating on relatively small images. This feature would be great if it would only operate faster.

At this point, I’ve realized I probably couldn’t do it directly from the command line, and I decided to implement a solution using Python and the Python Imaging Library (PIL). This was the start of my new project – biscan (blue ink scan).The program iterates over the pixels in the image and checks the blue level; if the blue level is high (but not too high), it forces the pixel to become black. The following code is still pretty experimental, and is the first prototype.

#!/usr/bin/python
"""
biscan - Blue Ink Scan 0.1. Takes a color image of a scanned document written using
blue ink, and turnes it into a blac-and-white (lineart) image.
(C) 2008 Guy Rutenberg. Released under the terms of the GPLv2.
"""
import sys
import Image
from optparse import OptionParser

def parseArguments():
    parser = OptionParser(usage="%prog [options] FILEIN FILEOUT", 
            version="%prog 0.1 ")
    parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False,
            help="be verbose")
    parser.add_option("-b", "--blue-threshold", dest="blue_threshold", type="int", default=220,
            help="Set the blue threshold (0..255) to NUM", metavar="NUM")

    return parser.parse_args()
def main(options, args):
    if len(args)<2:
        print "See --help for usage instructions"
        return 1

    im = Image.open(args[0])
    pix = im.load()

    for x in xrange(im.size[0]):
        for y in xrange(im.size[1]):
            if pix[x,y][2]<options.blue_threshold:
                pix[x,y] = (0,0,0)
        if options.verbose: print "proccessed line",x

    def threshold(i):
        if i<127: return 0
        return 255
    im.convert("L").point(threshold).save(args[1])

if __name__== '__main__':
    (options, args) = parseArguments()
    sys.exit(main(options, args))

See python biscan --help for options. This version outperforms all the above-mentioned methods in quality and the compression ratio achieved on the output. Its performance is reasonable, around 10 sec for an 8-mega-pixel image. I’ve tested it with several different blue inks (Uniball, Pilot, Ballograf, and some generic ones), and it operated pretty well on all of them.

So what next? I plan to improve the script and allow it to almost perfectly convert documents written in blue ink to B&W. I’m going to experiment with a new model for identifying the blue ink using HSL values (instead of RGB). The next version will also be more polished, as I’ve released this one under the motto of “release early” (and I hope I will also release often). So stay tuned for updates.

If you give this script a try, I will be glad to hear about it. Of course, if you have any questions, you’re welcome to contact me.

N.B. biscan requires PIL 1.1.6 (the latest one as of this time). PIL can be found in Gentoo under dev-python/imaging.

Leave a Reply

Your email address will not be published. Required fields are marked *