Turn Greyscale Images into Black and White with pamthreshold
A scanned page with patchy lighting defeats a single global cutoff, and pamthreshold fixes that with automatic, fixed, local or dual thresholding. You will finish with a black-and-white PAM image made from a greyscale PGM or PAM input, plus a way to convert it to PBM when an older tool needs that format. The examples use the installed Netpbm package, version 2:11.05.02-1.1build1, and the behaviour documented by its local pamthreshold(1) manual page.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need the netpbm package, a readable greyscale image, and enough free space for a new output file. These commands normally run as your ordinary user; skip sudo unless your input or destination permissions genuinely require it.
1. Check the input and installed command
pamthreshold expects a PGM image or a PAM image with tuple type GRAYSCALE or GRAYSCALE_ALPHA. It does not validate the tuple type, so feeding it a colour PPM can produce output that is technically written but not remotely useful. Check the command and input before choosing an algorithm:
$ command -v pamthreshold
/usr/bin/pamthreshold
$ dpkg-query -W -f='${Package} ${Version}\n' netpbm
netpbm 2:11.05.02-1.1build1
$ file input.pgm
input.pgm: Netpbm image data, size 1200 x 800, rawbits, greymap
The exact file wording varies, but the checks that matter are that the input exists, is readable, and is genuinely a greyscale Netpbm image. Keep the original file: pamthreshold reads it and writes its result to standard output.
Checkpoint
If command -v finds nothing, stop and install Netpbm through your normal package-management process. Do not swap in a similarly named image utility without checking its format and threshold semantics first.
2. Use the default automatic threshold
With no method option, the program calculates a global threshold using its iterative algorithm. Redirect standard output to a new destination, not the input:
$ pamthreshold input.pgm > output.pam
pamthreshold: using global threshold 0.38
That threshold value is image-dependent, and it is written to standard error while the PAM image itself goes to standard output. That split makes the normal redirection safe for the image data and still leaves the diagnostic visible in your terminal.
Check the result's header:
$ sed -n '1,8p' output.pam
P7
WIDTH 1200
HEIGHT 800
DEPTH 1
MAXVAL 1
TUPLTYPE BLACKANDWHITE
ENDHDR
Do not expect a PGM or PBM header here. The documented output is PAM with tuple type BLACKANDWHITE. A successful exit status only confirms the command ran; checking the header confirms the result actually has the format and dimensions you expected.
3. Set a fixed threshold for predictable results
Use -simple when you want one global threshold that you choose yourself. It is a floating-point value from 0 to 1, proportional to the input sample range. At the default of 0.5, pixels at least half as bright as the input maxval become white, and darker pixels become black:
$ pamthreshold -simple -threshold=0.5 input.pgm > output-fixed.pam
$ sed -n '1,8p' output-fixed.pam
P7
WIDTH 1200
HEIGHT 800
DEPTH 1
MAXVAL 1
TUPLTYPE BLACKANDWHITE
ENDHDR
The same option can be written with two hyphens or whitespace instead of the equals sign, for example --simple --threshold 0.5. Spell options out in full in scripts, so a future reader is not left resolving the manual's permitted abbreviations.
- Lower the threshold when faint foreground marks need to survive.
- Raise it when light background texture keeps getting mistaken for foreground.
- Always test on a new output name. Shell redirection with
>truncates an existing destination beforepamthresholdeven starts, so a failed run can destroy a useful earlier result.
4. Handle uneven lighting with local thresholding
A single global threshold is a poor fit for a scan or photograph whose background changes across the page. -local=WIDTHxHEIGHT calculates a threshold for each pixel from a centred neighbourhood. For example:
$ pamthreshold -local=31x31 -threshold=0.5 input.pgm > output-local.pam
$ sed -n '1,8p' output-local.pam
P7
WIDTH 1200
HEIGHT 800
DEPTH 1
MAXVAL 1
TUPLTYPE BLACKANDWHITE
ENDHDR
The two numbers describe the neighbourhood, not a resize. Larger neighbourhoods preserve broad regions better but run slower; local processing in general is slower than the default global method. Pick dimensions that match the scale of the marks you want separated, then inspect the actual image rather than trusting a plausible-looking header.
The -threshold value applies to simple and local thresholding. It means nothing under the default automatic method.
5. Try dual thresholding when contrast varies
-dual=WIDTHxHEIGHT combines a global threshold for low-contrast neighbourhoods with local thresholding everywhere else, which can preserve larger background or foreground areas better than purely local processing:
$ pamthreshold -dual=31x31 -threshold=0.5 -contrast=0.05 input.pgm > output-dual.pam
$ file output-dual.pam
output-dual.pam: Netpbm PAM file
-contrast is also a value from 0 to 1, defaulting to 0.05, and it only matters with -dual. Keep the defaults at first, then change one value at a time so you can tell which change actually moved the result.
Checkpoint
Compare the three output files in an image viewer or downstream conversion tool. A command can succeed while the chosen neighbourhood is too small, too large, or simply wrong for the image.
6. Convert PAM to PBM only when needed
The PAM result is the native output. If an older program cannot read PAM, or storage space matters, pass it through pamtopnm; for a black-and-white image that produces PBM:
$ pamtopnm output-fixed.pam > output-fixed.pbm
$ head -n 1 output-fixed.pbm
P4
The manual notes that PAM can use roughly eight times the space of PBM for this kind of image, so check the converted file before replacing anything downstream. This conversion does not touch either source file.
If the input has an alpha channel, the output is BLACKANDWHITE_ALPHA. Its maxval is 1, so transparency reduces to fully transparent or fully opaque. The installed manual records alpha handling as available since Netpbm 10.43, so do not assume an older Netpbm install behaves the same way.
Common traps
If the program rejects the input, check the file path, permissions and format without changing the file:
$ test -r input.pgm && echo 'input is readable'
$ file input.pgm
$ head -n 4 input.pgm
If a colour image was supplied, convert it to greyscale with a suitable Netpbm tool first, then run pamthreshold on that new file. Do not lean on the program's lack of input checking to make colour data meaningful on its own.
Recovery
If an output is wrong, keep the input and the earlier output, pick a new destination, and rerun with one changed method or value. There is no in-place undo for a redirection that has already truncated a file; recovery means restoring from a backup or recreating it from the original input. For future runs, write to output.pam.new, verify it, and only rename it into place once the check succeeds.
Done means
- The installed Netpbm version and input format were checked.
- A new PAM file has tuple type
BLACKANDWHITEorBLACKANDWHITE_ALPHA. - The chosen method matches the image: automatic, simple, local, or dual.
- Threshold and contrast defaults were not confused with options that belong to another method.
- PBM conversion happened only when a consumer actually required it.
- The original input and useful previous outputs remain recoverable.