Skip to content

Instantly share code, notes, and snippets.

@mweibel
Last active March 20, 2020 13:05
Show Gist options
  • Save mweibel/bd2d6c2271e42ed97b97 to your computer and use it in GitHub Desktop.
Save mweibel/bd2d6c2271e42ed97b97 to your computer and use it in GitHub Desktop.
Python OpenCV deskew function, based on http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
def compute_skew(image):
image = cv2.bitwise_not(image)
height, width = image.shape
edges = cv2.Canny(image, 150, 200, 3, 5)
lines = cv2.HoughLinesP(edges, 1, cv2.cv.CV_PI/180, 100, minLineLength=width / 2.0, maxLineGap=20)
angle = 0.0
nlines = lines.size
for x1, y1, x2, y2 in lines[0]:
angle += np.arctan2(y2 - y1, x2 - x1)
return angle / nlines
def deskew(image, angle):
image = cv2.bitwise_not(image)
non_zero_pixels = cv2.findNonZero(image)
center, wh, theta = cv2.minAreaRect(non_zero_pixels)
root_mat = cv2.getRotationMatrix2D(center, angle, 1)
rows, cols = image.shape
rotated = cv2.warpAffine(image, root_mat, (cols, rows), flags=cv2.INTER_CUBIC)
return cv2.getRectSubPix(rotated, (cols, rows), center)
deskewed_image = deskew(img.copy(), compute_skew(img))
@cpore
Copy link

cpore commented Feb 26, 2016

cols, rows = image.shape
should be:
rows, cols = image.shape

Also:
np.arctan2(x2 - x1, y2 - y1)
should be:
np.arctan2(y2 - y1, x2 - x1)

@Chuck-Aguilar
Copy link

Chuck-Aguilar commented Jul 5, 2016

For me it doesn't work so, I'had to make some features:


def compute_skew(image):
    # image = cv2.bitwise_not(image)
    height, width = image.shape

    edges = cv2.Canny(image, 150, 200, 3, 5)
    lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=width / 2.0, maxLineGap=20)
    angle = 0.0
    number_of_line = lines.size
    for x1, y1, x2, y2 in lines[0]:
        if x1 != x2:
            angle += np.arctan(y2 - y1 / x2 - x1)
    return angle / number_of_line


def deskew(image, angle):
    angle = np.math.degrees(angle)
    # image = cv2.bitwise_not(image)
    non_zero_pixels = cv2.findNonZero(image)
    center, wh, theta = cv2.minAreaRect(non_zero_pixels)

    root_mat = cv2.getRotationMatrix2D(center, angle, 1)
    rows, cols = image.shape
    rotated = cv2.warpAffine(image, root_mat, (cols, rows), flags=cv2.INTER_CUBIC)

    return cv2.getRectSubPix(rotated, (cols, rows), center)

@anselal
Copy link

anselal commented May 5, 2017

I tried to run your code with opencv 2.4.10 and I get some errors. Does it run only on version 3 ?

@hanfeisun
Copy link

It doesn't work for the picture attached below
p16-150x150

@avsthiago
Copy link

I'm using:
Numpy: 1.13.3
OpenCV: 3.3.0
Python: 3.6.4

For me, it was necessary to do some modifications in order to make it work in my environment:

import cv2
import numpy as np

img = cv2.imread('image_path', cv2.IMREAD_GRAYSCALE)

def compute_skew(image):
    image = cv2.bitwise_not(image)
    height, width = image.shape
    # Filter removed
    # edges = cv2.Canny(image, 150, 200, 3, 5)
    lines = cv2.HoughLinesP(image, 1, np.pi/180, 100, minLineLength=width / 2.0, maxLineGap=20)
    angle = 0.0
    # lines.size gets the number of lines multiplied by 4 (number of columns)
    # nlines = lines.size
    # so now, I only use the number of lines
    nlines = lines.size.shape[0]
    # this reshape was necessary in order to convert the shape from (n_lines,1,4) to (n_lines,4)
    lines = lines.reshape(lines.shape[0], 4)
    # [0] removed because of the new shape
    # for x1, y1, x2, y2 in lines[0]:
    for x1, y1, x2, y2 in lines:
        angle += np.arctan2(y2 - y1, x2 - x1)
    
    # The function cv2.getRotationMatrix2D recieves as input the
    # angle in degrees, so I converted the return
    # https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html#getrotationmatrix2d
    #return angle / nlines
    
    angle /= nlines
    return angle*180/np.pi


def deskew(image, angle):
    image = cv2.bitwise_not(image)
    non_zero_pixels = cv2.findNonZero(image)
    center, wh, theta = cv2.minAreaRect(non_zero_pixels)

    root_mat = cv2.getRotationMatrix2D(center, angle, 1)
    rows, cols = image.shape
    rotated = cv2.warpAffine(image, root_mat, (cols, rows), flags=cv2.INTER_CUBIC)

    return cv2.getRectSubPix(rotated, (cols, rows), center)


deskewed_image = deskew(img.copy(), compute_skew(img))

cv2.imshow('original', img)
cv2.imshow('deskew', deskewed_image)

I hope that helps someone 😄

@rainabba
Copy link

rainabba commented Sep 13, 2018

@avsthiago Yes, thank you! In return, I share the following JavaScript version (specifically for opencv4nodejs

function deskew( mat, angle, reqid = '0', drawGrid = false ) {
  angle = angle || computeSkew( mat.cvtColor(cv.COLOR_BGR2GRAY).bitwiseNot() );

  let nGray = mat.cvtColor(cv.COLOR_BGR2GRAY),
    mGray = nGray.bilateralFilter( 10, 60, 60 ),
    mEdge = mGray.canny(0 , 1, 5),
    contours = mEdge.findContours(cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE),
    rotatedRect = contours.sort( (c0, c1) => c1.area - c0.area )[0].minAreaRect(),
    initCAngle = rotatedRect.angle,
    contourAngle = rotatedRect.size.width < rotatedRect.size.height ? initCAngle + 90 : initCAngle;

  return mat.warpAffine( cv.getRotationMatrix2D( new cv.Point( mat.cols / 2, mat.rows / 2 ), contourAngle ) );
}

function computeSkew( mat ) { // mat is expected to already be grayscale and inverted
  let [ height, width ] = mat.sizes,
    lines = mat.houghLinesP(1, Math.PI/180, 100, minLineLength = width / 2.0, maxLineGap = 20),
    angle = lines.reduce((ac, line, index) => {
      return ac + Math.atan2( line.w - line.x, line.z - line.y )
    }, 0);

  return angle / lines.length * 180 / Math.PI;
}

@cresxjohn
Copy link

@avsthiago i got an error in 'nlines = lines.size.shape[0]', it says 'int' object has no attribute 'shape'

@Bech007
Copy link

Bech007 commented Apr 16, 2019

me too i got an error in 'nlines = lines.size.shape[0]', it says 'int' object has no attribute 'shape'

@ggamit1
Copy link

ggamit1 commented Dec 18, 2019

me too i got an error in 'nlines = lines.size.shape[0]', it says 'int' object has no attribute 'shape'

@danepeterpadley
Copy link

For those that are getting the error in 'nlines = lines.size.shape[0]', it says 'int' object has no attribute 'shape'.
You can resolve this issue by changing it to 'nlines = lines.shape[0]'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment