Victor Image Processing Library How-to Tips

Deskew an Image

Often after an document is scanned the resulting image appears to be slanted. To straighten the image we find a reference line and compute the amount of rotation necessary to bring it into alignment.

To rotate the image to make the reference line completely horizontal or vertical is to "deskew" the image.

The steps used to deskew an image.

original image
deskewed image
Original image with area of interest in dotted rectangle
width = 408 height = 233
Deskewed Image
width = 434 height = 282

In this example a line present in a table shown in the document is used as the reference line and the image is rotated to make that line horizontal.


VB Source Code | C Source Code


Deskew an Image - the Visual Basic Source Code

Type POINT
        x As Long
        y As Long
End Type
Private Sub mnudeskew_Click()
Dim rcode As Long
Dim rc As Long
Dim pt1 As POINT
Dim pt2 As POINT
' Operates on an image defined globally by the image descriptor, vimage 
	vimage.stx = 266 
	vimage.sty = 34 
	vimage.endx = 402 
	vimage.endy = 64
	
            ' Find a line in the image area, pt1 and pt2 will receive the coordinates of the endpoints 
   rcode = find_a_line(vimage, pt1, pt2)
   If (rcode = NO_ERROR) Then
            ' Deskew whole page based on line defined by pt1 and pt2
        rcode = deskewwholepage(vimage, pt1, pt2)
   End If
   If rcode = NO_ERROR Then
            ' Display the new image
        MainWnd.Picture1 = image_to_picturebox(vimage)
        MainWnd.Picture1.Refresh
    Else
            ' Handle any errors
        MainWnd.error_handler rcode, 0
    End If
End Sub
'Find black line on a white background, put the coordinates of the endpoints in leftmost and rightmost
Public Function find_a_line(ByRef srcimg As imgdes, ByRef leftmost As POINT, ByRef rightmost As POINT) As Long
Dim rcode, x, y, src_pixelval, limit As Long
Dim bmh As BITMAPINFOHEADER
    rcode = NO_ERROR
    getbmhfromimage bmh, vimage
            ' This function is for 1-bit and 8-bit grayscale only
   If (bmh.biBitCount = 1) Then
      limit = 1
   Else
        If (bmh.biBitCount = 8) Then
            limit = 128
        Else
           rcode = BAD_BPP
           GoTo xit
        End If
   End If
            ' Assume diagonal line exists in the image area
   leftmost.x = srcimg.endx
   leftmost.y = srcimg.endy
   rightmost.x = srcimg.stx
   rightmost.y = srcimg.sty
   For y = srcimg.sty To srcimg.endy
      For x = srcimg.stx To srcimg.endx
         src_pixelval = getpixelcolor(srcimg, x, y)
         If (src_pixelval < limit) Then
            If (x < leftmost.x) Then
               leftmost.x = x
               leftmost.y = y
               End If
            If (x > rightmost.x) Then
               rightmost.x = x
               rightmost.y = y
               End If
            End If
         Next x
      Next y
xit:
   find_a_line = rcode
End Function
Public Function deskewwholepage(ByRef srcimg As imgdes, ByRef pt1 As POINT, ByRef pt2 As POINT) As Long
Dim rcode As Long
Dim dx As Long
Dim dy As Long
Dim deltax As Long
Dim deltay As Long
Dim slope As Double
Dim angle As Double
Dim timage As imgdes
Dim bmh As BITMAPINFOHEADER
Const PI = 3.14159265358979        ' Atn(1) * 4
Const D2R As Double = PI / 180     ' Convert Deg to Rad
Const R2D As Double = 180 / PI     ' Convert Rad to Deg
    rcode = NO_ERROR
    getbmhfromimage bmh, vimage
            ' Determine slope of the line and then the angle
    deltay = (pt2.y - pt1.y)
    deltax = (pt2.x - pt1.x)
    If (deltax = 0) Then
        rcode = BAD_RANGE
        GoTo xit
    End If
    
    slope = deltay / deltax
    angle = Atn(slope)
    angle = R2D * angle
   
            ' Set to rotate entire image
   srcimg.stx = 0
   srcimg.sty = 0
   srcimg.endx = bmh.biWidth - 1
   srcimg.endy = bmh.biHeight - 1
   calcMinRotImageArea angle, srcimg, dx, dy
            ' Rotate the entire image
            ' Allocate space for the new image
   rcode = allocimage(timage, dx, dy, bmh.biBitCount)
   If (rcode = NO_ERROR) Then
            ' Set background to white
      zeroimage 255, timage
            ' Rotate image into timage
      rcode = rotate(angle, srcimg, timage)
      If (rcode = NO_ERROR) Then
            ' Success, copy palette into timage
         copyimagepalette srcimg, timage
            ' Success, free source image
         freeimage srcimg
            ' Assign timage to the source image
         copyimgdes timage, srcimg
         
      Else ' Error in rotating image, release timage memory
         freeimage timage
      End If
    End If
xit:
    deskewwholepage = rcode
End Function
Public Sub calcMinRotImageArea(ByRef angle As Double, ByRef srcimg As imgdes, ByRef dx As Long, ByRef dy As Long)
Dim tmpangle As Double
Dim cols As Long
Dim rows As Long
Const PI = 3.14159265358979        ' Atn(1) * 4
Const D2R As Double = PI / 180     ' Convert Deg to Rad
Const R2D As Double = 180 / PI     ' Convert Rad to Deg
    cols = srcimg.endx - srcimg.stx + 1
    rows = srcimg.endy - srcimg.sty + 1
            ' Calc the width and height of the rotated image area
    tmpangle = Abs(angle)
    Do While tmpangle > 90: tmpangle = tmpangle - 90: Loop
    If (tmpangle >= 0 And tmpangle <= 90) Or (tmpangle > 180 And tmpangle <= 270) Then
      dx = (Sin(D2R * (tmpangle)) * rows) + (Sin(D2R * (90 - tmpangle)) * cols)
      dy = (Sin(D2R * (tmpangle)) * cols) + (Sin(D2R * (90 - tmpangle)) * rows)
    ElseIf (angle > 90 And angle <= 180) Or (angle > 270 And angle < 360) Then
      dx = (Sin(D2R * (tmpangle)) * cols) + (Sin(D2R * (90 - tmpangle)) * rows)
      dy = (Sin(D2R * (tmpangle)) * rows) + (Sin(D2R * (90 - tmpangle)) * cols)
    End If
End Sub

Deskew an Image - the C/C++ Source Code

Requires Victor Image Processing Library v 5.3 or higher.
This example finds a line in an image area of interest
then rotates the entire image to make the line perfectly horizontal or vertical.
// ........... Helper function prototypes ...........
static void calcMinRotImageArea(double angDeg, imgdes far *image, int far *dcols, int far *drows);
int deskewwholepage(imgdes *srcimg, POINT pt1, POINT pt2);
int find_a_line(imgdes *srcimg, POINT * leftmost,  POINT * rightmost);
#define CALC_WIDTH(image)  ((image)->endx - (image)->stx + 1) // Width in pixels, defined in vicdefs.h
#define CALC_HEIGHT(image) ((image)->endy - (image)->sty + 1) // Height in pixels, defined in vicdefs.h
// Deskew command selected, operates on global image defined elsewhere in the program 
void DoDeskew(HWND hWnd) { 
int rcode, rc; 
POINT pt1, pt2;
	Image.stx = 266;
	Image.sty = 34;
	Image.endx = 402;
	Image.endy = 64;
// Find a line in the image area, pt1 and pt2 will receive the coordinates of the endpoints    
   rcode = find_a_line(&Image, &pt1, &pt2);
   if(rcode == NO_ERROR) {
      rc = MessageBox(hWnd, "Entire image will be rotated based on black line in image area",
               "Deskew the page", MB_OKCANCEL);
      if(rc == IDOK) 
            // Deskew whole page based on line defined by pt1 and pt2
         rcode = deskewwholepage(&Image, pt1, pt2);
      }
   if(rcode != NO_ERROR)
      error_handler(hWnd, rcode);   // Handle any errors 
}
// Rotates entire image to align make a line defined by pt1 - pt2 normal
int deskewwholepage(imgdes *srcimg, POINT pt1, POINT pt2)
{
    int rcode;
    double deltay, deltax, angle;
    int dx, dy;
    imgdes timage;
            // Determine slope of the line and then the angle
    deltay = (double)(pt2.y - pt1.y);
    deltax = (double)(pt2.x - pt1.x);
    angle = atan2(deltay, deltax);
    angle = RADTODEG(angle);
    if (angle >= 45.0)
       angle -= 90.0;
    else if (angle <= -45.0)
       angle += 90.0;
            // Set to rotate entire image 
   srcimg->stx = 0;
   srcimg->sty = 0,
   srcimg->endx = (UINT)srcimg->bmh->biWidth - 1;
   srcimg->endy = (UINT)srcimg->bmh->biHeight - 1;
   calcMinRotImageArea(angle, srcimg, &dx, &dy);     // see below
            // Rotate the entire image
            // Allocate space for the new image
   if((rcode = allocimage(&timage, dx, dy, srcimg->bmh->biBitCount)) == NO_ERROR) {
            // Set background to white
      zeroimage(255, &timage);
      /* Rotate image into timage */
      if((rcode = rotate(angle, srcimg, &timage)) == NO_ERROR) {
         /* Success, copy palette into timage */
         copyimagepalette(srcimg, &timage);
            // Success, free source image
         freeimage(srcimg);
            // Assign timage to the source image
         copyimgdes(&timage, srcimg);
         }
      else // Error in rotating image, release timage memory
         freeimage(&timage);
      }
    return(rcode);
}
// Find black line on a white background
int find_a_line(imgdes *srcimg, POINT * leftmost,  POINT * rightmost)
{
   int rcode = NO_ERROR;
   int x, y;
   int src_pixelval;
   int limit;
            // This function is for 1-bit and 8-bit grayscale only
   if (srcimg->bmh->biBitCount == 1) 
      limit = 1;
   else if(srcimg->bmh->biBitCount == 8) 
      limit = 128;
   else 
      return (BAD_BPP);
            // Assume diagonal line
   leftmost->x = srcimg->endx;
   leftmost->y = srcimg->endy;
   rightmost->x = srcimg->stx;
   rightmost->y = srcimg->sty;
   for(y = (int)srcimg->sty; y<=(int)srcimg->endy; y++) {   
      for (x = (int)srcimg->stx; x<=(int)srcimg->endx; x++) {   
         src_pixelval = getpixelcolor(srcimg, x, y);
         if(src_pixelval < limit) {
            if(x < leftmost->x) {
               leftmost->x = x;
               leftmost->y = y;
               }
            if(x > rightmost->x) {
               rightmost->x = x;
               rightmost->y = y;
               }
            }
         }
      }
   return(rcode);
}
// Calculate the cols and rows (in pixels) that will contain the rotated image area.  
static void calcMinRotImageArea(double angDeg, imgdes far *image,
   int far *dcols, // Dimensions of image area that can hold the 
   int far *drows) //  rotated source image
{
#define L_MULTIP 15 // Power of 2 to scale doubles to
#define MRD_FACTOR (double)(1L << L_MULTIP)
#define MRL_FACTOR (1L << L_MULTIP)
#define MRSCALE_UPD(dval) ((long)((dval) * MRD_FACTOR))
#define MRSCALE_DNL(lval) ((int)((lval + (MRL_FACTOR - 1)) / MRL_FACTOR))
#define PI 3.141592654
#define DEGTORAD(ang) ((ang) * PI / 180.0)
   unsigned cols, rows;
   long sintheta, costheta;
   double angRad;
            // Make sure angle lies between -360 and +360
   while(angDeg > 360.0)
      angDeg -= 360.0;
   while(angDeg < -360.0)
      angDeg += 360.0;
            // Convert angle to radians
   angRad = DEGTORAD(angDeg);
            // Calc a long int version of our sine and cosine
   sintheta = MRSCALE_UPD( sin(angRad) );
   costheta = MRSCALE_UPD( cos(angRad) );
   
            // Calc absolute values of sine and cosine
   if(sintheta < 0L)
      sintheta = -sintheta;
   if(costheta < 0L)
      costheta = -costheta;
   cols = CALC_WIDTH(image);
   rows = CALC_HEIGHT(image);
   *dcols = MRSCALE_DNL( costheta * cols + sintheta * rows );
   *drows = MRSCALE_DNL( sintheta * cols + costheta * rows );
}
Victor Image Processing Library

Victor Image Processing Library homepage | Victor Product Summary | more source code




Copyright 2002, 2005 Catenary Systems Inc. All rights reserved. Victor Image Processing Library is a trademark of Catenary Systems.