Skip to content

Instantly share code, notes, and snippets.

@karnauskas
Forked from russss/deskew.py
Created December 13, 2021 22:32
Show Gist options
  • Save karnauskas/988014dc2172a2fbc4f631fa22b2be62 to your computer and use it in GitHub Desktop.
Save karnauskas/988014dc2172a2fbc4f631fa22b2be62 to your computer and use it in GitHub Desktop.
Automatic scanned image rotation/deskew with OpenCV
import cv2
import numpy as np
def deskew(im, max_skew=10):
height, width = im.shape
# Create a grayscale image and denoise it
im_gs = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
im_gs = cv2.fastNlMeansDenoising(im_gs, h=3)
# Create an inverted B&W copy using Otsu (automatic) thresholding
im_bw = cv2.threshold(im_gs, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# Detect lines in this image. Parameters here mostly arrived at by trial and error.
lines = cv2.HoughLinesP(
im_bw, 1, np.pi / 180, 200, minLineLength=width / 12, maxLineGap=width / 150
)
# Collect the angles of these lines (in radians)
angles = []
for line in lines:
x1, y1, x2, y2 = line[0]
angles.append(np.arctan2(y2 - y1, x2 - x1))
# If the majority of our lines are vertical, this is probably a landscape image
landscape = np.sum([abs(angle) > np.pi / 4 for angle in angles]) > len(angles) / 2
# Filter the angles to remove outliers based on max_skew
if landscape:
angles = [
angle
for angle in angles
if np.deg2rad(90 - max_skew) < abs(angle) < np.deg2rad(90 + max_skew)
]
else:
angles = [angle for angle in angles if abs(angle) < np.deg2rad(max_skew)]
if len(angles) < 5:
# Insufficient data to deskew
return im
# Average the angles to a degree offset
angle_deg = np.rad2deg(np.median(angles))
# If this is landscape image, rotate the entire canvas appropriately
if landscape:
if angle_deg < 0:
im = cv2.rotate(im, cv2.ROTATE_90_CLOCKWISE)
angle_deg += 90
elif angle_deg > 0:
im = cv2.rotate(im, cv2.ROTATE_90_COUNTERCLOCKWISE)
angle_deg -= 90
# Rotate the image by the residual offset
M = cv2.getRotationMatrix2D((width / 2, height / 2), angle_deg, 1)
im = cv2.warpAffine(im, M, (width, height), borderMode=cv2.BORDER_REPLICATE)
return im
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment