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 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.
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
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 homepage | Victor Product Summary | more source code