Process Scanned Photo
Straighten the Print, Kill the Scanner Bed
- image
- IMAGE
You put a stack of family prints on a flatbed, hit scan, and got back a very white rectangle with a photograph somewhere inside it, rotated 2.3 degrees because you didn't line it up with the ruler. Process Scanned Photo exists for exactly that: find the print, straighten it, and throw away everything that isn't the print.
It's deliberately narrow. The pack's Intelligent AutoCrop handles generic borders and text-bearing footers on already-flat images; this node handles the specific case where the "border" is a scanner bed and the photo is sitting on it at an angle.
How it works
Detection is brightness, edges, and contours - no semantic model, no ML. The engine samples the corners of the scan to estimate the bed's colour and how uniform the bed actually is, uses your threshold to decide whether that corner background is bright enough to be a scanner bed at all, then builds a foreground mask and finds the dominant rectangular component in it. The candidate has to be convincingly rectangle-shaped and adequately filled, or the node declines rather than inventing a crop. Rotation comes from the fitted rectangle's angle.
Analysis runs on a downsampled copy (long side capped at 2048) for speed, but the crop and rotation are applied to the full floating-point image - there's no 8-bit round trip in the middle, which matters on a 16-bit TIFF scan.
The inputs
Four, and you can leave most of them alone.
straighten (on by default) estimates the print's rotation and rotates the scan before cropping. Turn it off if the print was already square to the glass - and you'll notice if it's wrong, because a wrong angle estimate on a near-square print shows up immediately.
crop_mode is the real decision:
- Inner Photo Frame (default) - targets the image area inside the print's own border. Use this for a Polaroid, a bordered 6×4, anything with a printed frame you want gone.
- Scanner Bed Only - targets the outer print boundary, keeping the print's own border. The white bed goes, the Polaroid frame stays.
- None - no crop at all, straightening still applies. The escape hatch.
padding (default 0) keeps N pixels around the detected crop; negative values crop further inward. Use a small positive number when the detector is clipping a millimetre off a print edge, and negative when it's leaving a sliver of bed behind.
threshold (default 0.8) is how bright a pixel has to be before it's treated as scanner bed. Lower it if your bed is grey or the scan is underexposed and nothing is being detected; raise it if a genuinely white part of the photograph is being eaten as background.
One output: the processed IMAGE. No mask, no subject detection, no editor sizing - chain it into the rest of the workflow by hand.
Where it fits
Feed it before everything else on a scanned path: Process Scanned Photo → Intelligent AutoCrop (if there's still a border) → masks → Smart Photo Prepare → editor → upscaler. Doing it in this order matters, because subject masks have to be generated on the same pixel grid as the image the cropper sees. If you generate a mask on the raw scan and then straighten and crop, it won't line up, and preparation will tell you so instead of stretching it.
For genuinely damaged prints - cracking, heavy noise, blown highlights - this node is only the first step. The reconstruction work happens later in the chain (an editor pass, or a restoration-focused upscaler), and the honest expectation there is the same as anywhere else in photo restoration: the model invents plausible detail, so keep faces you need to stay accurate out of the aggressive stages.
Install
ComfyUI Manager → search PortraitUtils → install → restart the ComfyUI server. A browser refresh is not enough; Python nodes load at server start.
cd ComfyUI/custom_nodes
git clone https://github.com/heyburns/PortraitUtils
/path/to/ComfyUI/venv/bin/python -m pip install -r PortraitUtils/requirements.txt
Use ComfyUI's own Python - or python_embeded on a Windows portable build. This node's dependency is OpenCV (the pack pins opencv-python-headless), so install exactly one OpenCV wheel variant in that environment; overlapping variants share the cv2 namespace and produce confusing import errors. If you scan compressed 16-bit TIFFs, the loader will tell you when the optional imagecodecs package is needed.
What goes wrong
A white print border against a white scanner bed is genuinely ambiguous. If your Polaroid's frame is the same white as the glass, the detector has a brightness-only basis for distinguishing them, and it will sometimes guess wrong. Look at the output; adjust threshold a little in whichever direction the error is, and reach for crop_mode: Scanner Bed Only when you'd rather keep the frame than risk losing the photo.
Non-rectangular or heavily curled prints are outside this node's remit. It fits rectangles.
Don't expect it to fix exposure or colour. It's geometry. White balance and colour matching are separate nodes, and fixing colour with a generative pass is usually the wrong tool - the order of operations is geometry first, then a deterministic correction, then generation only if the pixels genuinely need rebuilding.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| straighten | BOOLEAN | true | — |
| crop_mode | COMBO | Inner Photo Frame | 3 options: Inner Photo Frame, Scanner Bed Only, None |
| padding | INT | 0-500–500 | — |
| threshold | FLOAT | 0.800–1 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |