Vladimir M. Vladimir M. - 5 months ago 15
Java Question

How to remove gray background from scanned image

I wrote a small Java SANE-client for Windows using jfreesane library and it worked well for some time but yesterday I found a problem with scanner CanoScan LiDE 60 - when using "grayscale" mode the scanned image has gray background and I don't know how to get rid of it. Here's what I'm talking about:

enter image description here

I ran into this problem with some other scanners and solved it by tuning the contrast parameter, however CanoScan LiDE 60 doesn't have one. Here is the full list of options from "scanimage -A" output (I tried all that relates to color except gamma-table):

All options specific to device `genesys:libusb:003:002':
Scan Mode:
--mode Color|Gray|Lineart [Gray]
Selects the scan mode (e.g., lineart, monochrome, or color).
--source Flatbed|Transparency Adapter [inactive]
Selects the scan source (such as a document-feeder).
--preview[=(yes|no)] [no]
Request a preview-quality scan.
--depth 8|16 [8]
Number of bits per sample, typical values are 1 for "line-art" and 8
for multibit scans.
--resolution 1200|600|300|150|75dpi [75]
Sets the resolution of the scanned image.
Geometry:
-l 0..218mm [0]
Top-left x position of scan area.
-t 0..299mm [0]
Top-left y position of scan area.
-x 0..218mm [218]
Width of scan-area.
-y 0..299mm [299]
Height of scan-area.
Enhancement:
--custom-gamma[=(yes|no)] [no]
Determines whether a builtin or a custom gamma-table should be used.
--gamma-table 0..65535,... [inactive]
Gamma-correction table. In color mode this option equally affects the
red, green, and blue channels simultaneously (i.e., it is an intensity
gamma table).
--red-gamma-table 0..65535,... [inactive]
Gamma-correction table for the red band.
--green-gamma-table 0..65535,... [inactive]
Gamma-correction table for the green band.
--blue-gamma-table 0..65535,... [inactive]
Gamma-correction table for the blue band.
--swdeskew[=(yes|no)] [no]
Request backend to rotate skewed pages digitally
--swcrop[=(yes|no)] [no]
Request backend to remove border from pages digitally
--swdespeck[=(yes|no)] [no]
Request backend to remove lone dots digitally
--despeck 1..9 (in steps of 1) [1]
Maximum diameter of lone dots to remove from scan
--swskip 0..100% (in steps of 1) [0]
Request driver to discard pages with low numbers of dark pixels
--swderotate[=(yes|no)] [no]
Request driver to detect and correct 90 degree image rotation
Extras:
--lamp-off-time 0..60 [15]
The lamp will be turned off after the given time (in minutes). A value
of 0 means, that the lamp won't be turned off.
--lamp-off-scan[=(yes|no)] [no]
The lamp will be turned off during scan.
--threshold 0..100% (in steps of 1) [50]
Select minimum-brightness to get a white point
--threshold-curve 0..127 (in steps of 1) [50]
Dynamic threshold curve, from light to dark, normally 50-65
--disable-dynamic-lineart[=(yes|no)] [no]
Disable use of a software adaptive algorithm to generate lineart
relying instead on hardware lineart.
--disable-interpolation[=(yes|no)] [no]
When using high resolutions where the horizontal resolution is smaller
than the vertical resolution this disables horizontal interpolation.
--color-filter Red|Green|Blue|None [None]
When using gray or lineart this option selects the used color.
Sensors:
Buttons:
--clear-calibration
Clear calibration cache


And here's an interesting thing: Simple Scan (Linux scanning program) somehow manages to remove gray from image with this scanner:

enter image description here

I'm not sure if it uses some post-scan processing or if it knows how to tell the scanner about contrast. If it's the former, are there any Java libraries that can remove this gray noise?

Answer

After some tests I did end up with this function (the contrast formula was taken from here):

public void processImage(BufferedImage bimg, int brightness, int contrast, int t_black, int t_white) {
        Color c;
        int r, g, b;
        float factor = (259f * (contrast + 255f)) / (255f * (259f - contrast));

        for (int x = 0; x < bimg.getWidth(); x++)
            for (int y = 0; y < bimg.getHeight(); y++) {
                c = new Color(bimg.getRGB(x, y));
                // apply brightness and contrast
                r = Math.round(factor * (c.getRed() - 128) + 128) + brightness;
                g = Math.round(factor * (c.getGreen() - 128) + 128) + brightness;
                b = Math.round(factor * (c.getBlue() - 128) + 128) + brightness;
                // limit to [0, 255] range
                r = Math.min(255, Math.max(0, r));
                g = Math.min(255, Math.max(0, g));
                b = Math.min(255, Math.max(0, b));
                // apply black and white thresholds
                if (r < t_black && g < t_black && b < t_black)
                    bimg.setRGB(x, y, 0);
                else if (r > t_white && g > t_white && b > t_white)
                    bimg.setRGB(x, y, 255 << 16 | 255 << 8 | 255);
                else
                    bimg.setRGB(x, y, r << 16 | g << 8 | b);
            }
    }