Skip to content

RESOURCES / BLOG

How do I restore a damaged photo?

Old family prints, film scans, or legacy JPEGs often arrive with scratches, dust, fading, and blur. In community threads, folks swap tips ranging from Photoshop healing brushes to Python inpainting.

I found a box of old photos, and several are torn, faded, and covered with scratches. I can scan them, but I want to do this right and keep a high-quality “master” while producing a shareable version for the web. How do I restore a damaged photo? Which steps should I follow in order, and is there a way to automate parts of the cleanup with code? Any tips for handling color cast and blur would be appreciated.

Photo restoration works best when you preserve a high-quality source, fix damage in a predictable order, and avoid destructive edits. The process presented here can be used for both individual portraits and multiple scans.

  • Clean the scanner glass and the photo surface lightly with a blower.
  • Scan at 600 to 1200 DPI for small prints; save a 16-bit TIFF or PNG master.
  • Avoid initial JPEG saves. Learn how resolution interacts with print size in this primer on DPI vs pixels.

Below is a minimal script using OpenCV and scikit-image to do color correction, denoising, and inpainting for light scratches. 

Install deps: pip install opencv-python scikit-image numpy

import cv2 as cv
import numpy as np
from skimage import restoration, img_as_float

# 1) Load and convert to working space
img = cv.imread("scan.tif"# use your high-DPI scan
orig = img.copy()

# 2) Global color correction with LAB + CLAHE
lab = cv.cvtColor(img, cv.COLOR_BGR2LAB)
L, A, B = cv.split(lab)
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
L_eq = clahe.apply(L)
lab_eq = cv.merge((L_eq, A, B))
img = cv.cvtColor(lab_eq, cv.COLOR_LAB2BGR)

# 3) Denoise without destroying edges
img = cv.fastNlMeansDenoisingColored(img, None, 5, 5, 7, 21)

# 4) Detect bright scratches and inpaint
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Emphasize thin lines
highpass = cv.Laplacian(gray, cv.CV_16S, ksize=3)
hp = cv.convertScaleAbs(highpass)
_, mask = cv.threshold(hp, 20, 255, cv.THRESH_BINARY)
mask = cv.morphologyEx(mask, cv.MORPH_OPEN, np.ones((3,3), np.uint8), iterations=1)
restored = cv.inpaint(img, mask, 3, cv.INPAINT_TELEA)

# 5) Optional mild sharpening
sharpen_kernel = np.array([[0, -1, 0],
                          [-1, 5, -1],
                          [0, -1, 0]])
restored = cv.filter2D(restored, -1, sharpen_kernel)

cv.imwrite("restored_master.png", restored)  # keep a high-quality masterCode language: PHP (php)

Notes:

  • For larger tears, manually draw a mask of the missing area and pass it to cv.inpaint.
  • For strong motion blur, try deconvolution. If you can estimate a motion kernel, you can use Wiener filtering with scikit-image.
# Simple illustrative Wiener deconvolution for mild motion blur
from skimage.restoration import wiener

def motion_psf(length=9, angle=0):
    psf = np.zeros((length, length))
    psf[length // 2, :] = 1
    psf /= psf.sum()
    return psf

imgf = img_as_float(cv.cvtColor(restored, cv.COLOR_BGR2GRAY))
psf = motion_psf(length=9)
deblur = wiener(imgf, psf, balance=0.01)
deblur_u8 = (np.clip(deblur, 0, 1) * 255).astype("uint8")
merged = cv.cvtColor(deblur_u8, cv.COLOR_GRAY2BGR)
cv.imwrite("restored_deblur.png", merged)Code language: PHP (php)
  • Use gentle contrast and color balance adjustments after repair.
  • Retain a 16-bit PNG or TIFF for the master, then create delivery JPGs at appropriate sizes.
  • Keep file size efficient for the web with formats and compression that preserve detail. See this guide on how to reduce image file size.

Once you have a solid master, you can offload enhancement and delivery to Cloudinary. For example, you can apply on-the-fly improvements like auto contrast, noise reduction, sharpening, and format optimization using a simple URL transformation:

https://res.cloudinary.com/<cloud_name>/image/upload/
  e_improve/e_noise_reduction:50/e_sharpen,q_auto,f_auto,w_1600/<public_id>.png

If you prefer a quick visual tool for upscaling, try the Image Upscale tool. You can also keep your archival master intact and generate multiple versions on demand, sized for web, email, or print previews without re-editing the original.

  • Scan cleanly at high resolution and keep a 16-bit PNG or TIFF master.
  • Restore in order: color balance, denoise, inpaint scratches, then mild sharpening.
  • Automate the boring parts with OpenCV and scikit-image.
  • Use cloud transformations to enhance and deliver optimized versions without touching your master.

Ready to streamline restoration, optimization, and delivery for your photos and scans? Sign up for a free Cloudinary account and start transforming images on the fly while keeping your masters safe.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free