I have been searching high and low for a reliable way to deskew an image in .Net, and am not having much luck.
At the minute I am using Aforge. This is a pain as I am working with WPF, so the images I am working with are BitmapImage objects, as opposed to Bitmap objects, meaning I need to start with a BitmapImage object, save this to a memory stream, create a new Bitmap object from the memory stream, go through the deskewing process, save the deskewed image to a new memory stream and then create a new BitmapImage object from said memory stream. Not only that, but the deskewing isn't great.
I am trying to read OMR data of a piece of paper scanned into a scanner, and therefore I need to rely on a particular OMR box being at the same co-ordinates every time, so the deskewing needs to be reliable.
So I am using Aforge at the minute, I can't find any other free/open source libraries for image deskewing in .Net, everything I have found is either properly expensive or in C/C++.
My question is do other free/open source libraries exist that assist in image deskewing in .Net? If so what are they called, if not how should I approach this problem?
Edit: For example, let's say I have the below page:
Note: This is for illustrative purposes only, but the actual image does indeed have a black rectangle at each corner of the page, maybe this will help.
When I print this out, and scan it back into my scanner, it looks like this:
I need to deskew this image so that my box is in the same place each time. In the real world, there are a lot of boxes, they are smaller and close together, so the accuracy is important.
My current method for this is a massive ineffective pain-in-the-ass:
using AForge.Imaging;
using AForge.Imaging.Filters;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;
public static BitmapImage DeskewBitmap(BitmapImage skewedBitmap)
{
//Using a memory stream to minimise disk IO
var memoryStream = BitmapImageToMemoryStream(skewedBitmap);
var bitmap = MemoryStreamToBitmap(memoryStream);
var skewAngle = CalculateSkewAngle(bitmap);
//Aforge needs a Bppp indexed image for the deskewing process
var bitmapConvertedToBbppIndexed = ConvertBitmapToBbppIndexed(bitmap);
var rotatedImage = DeskewBitmap(skewAngle, bitmapConvertedToBbppIndexed);
//I need to convert the image back to a non indexed format to put it back into a BitmapImage object
var imageConvertedToNonIndexed = ConvertImageToNonIndexed(rotatedImage);
var imageAsMemoryStream = BitmapToMemoryStream(imageConvertedToNonIndexed);
var memoryStreamAsBitmapImage = MemoryStreamToBitmapImage(imageAsMemoryStream);
return memoryStreamAsBitmapImage;
}
private static Bitmap ConvertImageToNonIndexed(Bitmap rotatedImage)
{
var imageConvertedToNonIndexed = rotatedImage.Clone(
new Rectangle(0, 0, rotatedImage.Width, rotatedImage.Height), PixelFormat.Format32bppArgb);
return imageConvertedToNonIndexed;
}
private static Bitmap DeskewBitmap(double skewAngle, Bitmap bitmapConvertedToBbppIndexed)
{
var rotationFilter = new RotateBilinear(-skewAngle) { FillColor = Color.White };
var rotatedImage = rotationFilter.Apply(bitmapConvertedToBbppIndexed);
return rotatedImage;
}
private static double CalculateSkewAngle(Bitmap bitmapConvertedToBbppIndexed)
{
var documentSkewChecker = new DocumentSkewChecker();
double skewAngle = documentSkewChecker.GetSkewAngle(bitmapConvertedToBbppIndexed);
return skewAngle;
}
private static Bitmap ConvertBitmapToBbppIndexed(Bitmap bitmap)
{
var bitmapConvertedToBbppIndexed = bitmap.Clone(
new Rectangle(0, 0, bitmap.Width, bitmap.Height), PixelFormat.Format8bppIndexed);
return bitmapConvertedToBbppIndexed;
}
private static BitmapImage ResizeBitmap(BitmapImage originalBitmap, int desiredWidth, int desiredHeight)
{
var ms = BitmapImageToMemoryStream(originalBitmap);
ms.Position = 0;
var result = new BitmapImage();
result.BeginInit();
result.DecodePixelHeight = desiredHeight;
result.DecodePixelWidth = desiredWidth;
result.StreamSource = ms;
result.CacheOption = BitmapCacheOption.OnLoad;
result.EndInit();
result.Freeze();
return result;
}
private static MemoryStream BitmapImageToMemoryStream(BitmapImage image)
{
var ms = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(ms);
return ms;
}
private static BitmapImage MemoryStreamToBitmapImage(MemoryStream ms)
{
ms.Position = 0;
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = ms;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
private static Bitmap MemoryStreamToBitmap(MemoryStream ms)
{
return new Bitmap(ms);
}
private static MemoryStream BitmapToMemoryStream(Bitmap image)
{
var memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Bmp);
return memoryStream;
}
In retrospect, a couple more questions:
- Am I using AForge correctly?
- Is AForge the best library to use for this task?
- How could my current approach to this be improved to get more accurate results?