A fast drop-in alternative to the excellent deskew library by sbrunner, using OpenCV instead of scikit-image for the heavy lifting. It estimates the skew angle of a document image so you can rotate it back to straight.
On sbrunner's own test set it returns the same angles while running ~9× faster (see Benchmark).
- Using pip:
pip install fast-deskew-cv
- From source:
pip install git+https://git@github.com/HOZHENWAI/fast_deskew.git
import cv2
from deskew import determine_skew
image_grayscale = cv2.imread('input_image.png', cv2.IMREAD_GRAYSCALE)
skew_angle = determine_skew(image_grayscale)determine_skew returns the skew angle in degrees (or None if no dominant
orientation is found). To straighten the image, rotate it by +skew_angle:
import cv2
from deskew import determine_skew
image = cv2.imread('input_image.png')
grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
angle = determine_skew(grayscale)
if angle is not None:
height, width = image.shape[:2]
matrix = cv2.getRotationMatrix2D((width / 2, height / 2), angle, 1.0)
deskewed = cv2.warpAffine(image, matrix, (width, height),
borderValue=(255, 255, 255))
cv2.imwrite('output_image.png', deskewed)The classical Hough-transform skew-detection pipeline:
- Blur the image (Gaussian) to suppress noise.
- Canny edge detection — thresholds are derived from a percentile of the image's gradient magnitude, so the same defaults work across document sizes and contrasts.
- Hough line transform to find straight edges.
- Fold every line orientation into a canonical interval (so text baselines and vertical strokes, which differ by ~90°, reinforce the same estimate) and return the most frequent (modal) angle from the strongest lines.
| Parameter | Default | Description |
|---|---|---|
image_array |
– | 8-bit single-channel (grayscale) image. |
sigma |
3.0 |
Standard deviation of the Gaussian pre-blur. |
num_peaks |
20 |
Number of strongest Hough lines used to vote on the angle. |
angle_pm_90 |
False |
Fold into [-90, 90) instead of [-45, 45). |
min_angle / max_angle |
None |
Restrict the reported skew to a degree range. |
min_deviation |
1.0 |
Angular resolution in degrees (also the voting bin width). |
gradient_percentile |
99.0 |
Gradient-magnitude percentile used as the high Canny threshold when auto-deriving. |
canny_threshold_low / canny_threshold_high |
None |
Explicit Canny thresholds; None auto-derives them from the gradient. |
hough_rho |
1.0 |
Distance resolution of the Hough accumulator (pixels). |
hough_threshold |
30 |
Minimum Hough votes for a line (a floor; peak selection stays relative via num_peaks). |
fast-deskew vs the reference deskew (1.6.1) on the reference library's own 8
test images. Both libraries receive the same grayscale array; timing is the
median of repeated calls and excludes image loading. Angles are compared against
the ground-truth values from the reference test suite.
| Image | Size | Ground truth | deskew |
fast-deskew |
Speed-up |
|---|---|---|---|---|---|
| 1 | 6172×4152 | −1.0° | −1.0° / 2781 ms | −1.0° / 320 ms | 8.7× |
| 2 | 1748×1241 | −2.0° | −2.0° / 300 ms | −2.0° / 31 ms | 9.7× |
| 3 | 1748×1241 | −6.0° | −6.0° / 286 ms | −6.0° / 27 ms | 10.5× |
| 4 | 1748×1241 | 7.0° | 7.0° / 238 ms | 7.0° / 30 ms | 7.9× |
| 5 | 724×1198 | 3.0° | 3.0° / 103 ms | 3.0° / 10 ms | 10.1× |
| 6 | 1415×1120 | −3.0° | −3.0° / 209 ms | −3.0° / 20 ms | 10.6× |
| 7 | 575×336 | 3.0° | 3.0° / 28 ms | 3.0° / 3.5 ms | 7.9× |
| 8 | 3089×2435 | 15.0° | 15.0° / 702 ms | 15.0° / 73 ms | 9.6× |
Accuracy: 8/8 angles identical to the reference. Speed: ~9× faster (median).
Measured on Python 3.14, OpenCV 4.13, scikit-image 0.26, AMD64. Absolute timings are hardware-dependent; the relative speed-up is the meaningful figure.
- Input is expected to be an 8-bit grayscale image (convert color images with
cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)first). - For skews beyond ±45°/±90°, fix the global page rotation first and use this
library for the residual fine skew.
angle_pm_90=Truewidens the fold range.
pip install -e ".[dev]"
pytest
MIT — see LICENSE.
Algorithm and test images from sbrunner/deskew.