Batch-processing images of documents to look like a fax

4

4

Let's say I have a photo or scan of a text document, possibly with some low-contrast watermark background. If it is a photo, in addition to the watermark there will be brightness gradients from the lighting and possibly from the sheet of paper not laying flat due to folds.

I want to postprocess these photos with imagemagick to look like a fax, i.e. converting the image to monochrome black/white correcting local variations of brightness. The normal -threshold option will not work, since

  1. It doesn't automatically detect the required brightness level for each photo.
  2. Due to the brightness gradients the text on one part of the image may be brighter than the background on another part such that for any given global threshold some text will be lost.

Cam-Scanner apps on smartphones usually provide a black/white-document option that corrects such color gradients and calculates a reasonable first guess for the threshold value, that would suffice for batch processing.

They do not help though when I have the raw image on PC already, though I could in theory upload them to the smartphone and import them -- it is just highly impractical, especially for large numbers of images.

Does imagemagick or some other batch-processing capable software (preferably open-source) support such a conversion?

kdb

Posted 2013-12-26T16:14:26.087

Reputation: 1 076

Rather than editing the links into the question, please post them as a separate answer or comment below the existing answer. Thanks. – slhck – 2015-08-19T15:54:01.933

Answers

7

You can use Imagemagick's Mathematical Compose Methods to achieve such results. Divide_src [1] in particular as it would remove any gradients, vignettes, unwanted shadings.

Then a -normalize and a -threshold should do the rest.

convert $input -colorspace gray ( +clone -blur 15,15 ) -compose Divide_Src -composite -normalize -threshold 80% $output

Here's my result:

You may want to adujst the threshold to get the best results.

Depending ot the OS you are going to run this you might have to escape the brackets: "\(" and "\)".

As for the batch processing personally I would use a "for" loop either in bash or in Cygwin again depending on the OS:

for file in test/*; do convert $file -colorspace gray ( +clone -blur 15,15 ) -compose Divide_Src -composite -normalize -threshold 80% result/`basename $file`; done

However there is another command line tool you might want to check out called mogrify [2] for inline or specific -path batch processing.

For more information and possibly different results follow [3] and [4].


[1]: www.imagemagick.org/Usage/compose/#divide

[2]: www.imagemagick.org/script/mogrify.php

[3]: staff.washington.edu/corey/camscan/

[4]: www.imagemagick.org/Usage/photos/#color-in

matthewd

Posted 2013-12-26T16:14:26.087

Reputation: 311

Thank you, worked like charm, although in practically every shell on linux you have to escape the parentheses. Write \( and \) instead of ( and ). – Socowi – 2019-05-21T09:59:17.833

Thanks for the answer. It seems to do what I need (removal of decolorings etc). Now I only need to find a method to make weak lines (e.g. pencil lines) more readable (but that would be a separate question). – kdb – 2014-01-01T18:58:34.300

You can try link. This script produces two files: one which is more fax like (with -threshold) and one which is grayscale (not pure B&W) and is more scan like (with brightness and contrast through -level). I think that pencil lines would be more visible with the grayscale one.

– matthewd – 2014-01-02T11:54:05.697

Just tried it on my example image. On neither outputfile even the printed text is readable and the grayscale output file is twice as big as the input colored filed (both JPEG). To be fair though, the color image is a rather bad color-photograph with the pencil lines being hard to read even on the original image. – kdb – 2014-01-03T12:33:00.527

If you post an example I might be able to come up with something. – matthewd – 2014-01-06T09:05:01.590

2

Update Updated forms of the scripts are now hosted as gists [1] [2]

Based on the answer my matthewd I have written scripts that automate the process as intended for scans with a reasonable contrast. The scripts use poppler's pdfimages, ImageMagick's convert and pdftk.

imagemagick-scan-pdf-to-mono.sh (depends on the second script; Output might be rotated which can be fixed by running pdftk FILE.pdf cat 1-endW output OUT.pdf. Rotation direction can be changed by using 1-endE instead of 1-endW)

#!/usr/bin/env bash
# -*- mode: sh; coding: us-ascii-unix -*-

# source libstacktrace || true
# set -e -u -E

MANUAL="
Usage: $0 [options] INPUT OUTPUT

Converts a scan-pdf (assuming one image per page) to monochrome.

-f INT, --from-page INT
    Process only pages with page number >= INT

-t INT, --to-page INT
    Process only pages with page number <= INT

-P, --parallel INT
    Process INT pages in parallel each. 

-v, --verbose / +v, --noverbose
    Enables/Disables verbose reporting.

-h, -?, --help
    Prints this message

"

vecho(){ $VERBOSE && echo "$@"; }

######### COMMAND LINE PARSING #######################################

declare VERBOSE=false
declare ARGS=()
declare PAGE_LIMIT_LOW=""
declare PAGE_LIMIT_HIGH=""
declare PARALLEL=1

## Print manual
if [[ $# -eq 0 ]]; then
    echo "$MANUAL"
    exit 1
fi

## Getopt-style consumption of arguments ##
##
## Don't forget "shift", don't delete "--" and "*" cases.
while [[ $# -gt 0 ]]; do
    case "$1" in
    -h|-\?|--help)
        echo "$MANUAL"
        exit 0
        shift ;;
    -v|--verbose)
        VERBOSE=true
        shift ;;
    +v|--no-verbose)
        VERBOSE=false
        shift ;;
    -f|--from-page)
        PAGE_LIMIT_LOW="-f $2"
        shift 2 ;;
    -t|--to-page)
        PAGE_LIMIT_HIGH="-l $2"
        shift 2 ;;
    -P|--parallel)
        PARALLEL=$2
        shift 2 ;;
    --)
        shift
        break ;;
    *) 
        ARGS[${#ARGS[@]}]="$1"
        shift ;; 
    esac
done

## Consume stuff remaining after -- ##
while [[ $# -gt 0 ]]; do 
    ARGS[${#ARGS[@]}]="$1"
    shift
done

## Note that ${ARGS[@]} is considered unbound if it is empty!

INFILE=$(readlink -m "${ARGS[0]}") 
OUTFILE=$(readlink -m "${ARGS[1]}")
TMPDIR=$(mktemp -d)

vecho "Using work directory '$TMPDIR'."
cd "$TMPDIR"
vecho "Extracting images from '$INFILE'..."

## Cannot be parallelized, file-locking issue. 

cmd="pdfimages -j $PAGE_LIMIT_LOW $PAGE_LIMIT_HIGH $(printf %q "$INFILE") page"
# vecho "$cmd"
eval "$cmd" || true

find -name "page-*" -and -not -name "page-*-mono*" \
    | xargs -P $PARALLEL -I FILE sh -c "
      imagemagick-scan-to-mono.sh FILE FILE-mono.pdf \
         && { if $VERBOSE; then echo Finished file 'FILE'; fi; }
      rm FILE
    "

vecho "Assembling PDF file '$OUTFILE'..."
pdftk page-*-mono.pdf cat output out.pdf 
mv out.pdf "$OUTFILE"
rm page-*-mono.pdf
rmdir "$TMPDIR" || ls -l

imagemagick-scan-to-mono.sh

#!/usr/bin/env bash
# -*- mode: sh; coding: us-ascii-unix -*-

source libstacktrace || true
set -e -u -E

MANUAL="
Usage: $0 INFILE OUTFILE

Takes a document scan INFILE (an image) and produces a monochromatized
output file version.

"

if [[ "${1:-}" = "-?" ]] || [[ "${1:-}" = "-h" ]] || [[ "${1:-}" = "--help" ]]; then 
    echo "$MANUAL"
    exit 0
fi

BLURRADIUS="20"
INFILE="$(readlink -m "$1")"
OUTFILE="$(readlink -m "$2")"
TMPDIR="$(mktemp -d)"
cd "$TMPDIR"

convert "$INFILE" -colorspace Gray 01.png
convert 01.png -blur "${BLURRADIUS}x${BLURRADIUS}" 02.tif
convert 01.png 02.tif -compose Divide_Src -composite 03.tif 
convert 03.tif -threshold 90% -type bilevel -compress group4 "$OUTFILE"

rm 01.png 02.tif 03.tif
rmdir "$TMPDIR"

kdb

Posted 2013-12-26T16:14:26.087

Reputation: 1 076