我一直在寻找一种可靠的方法来破坏.Net中的图像,而且我没有太多运气。
我正在使用Aforge。这是一个痛苦,因为我正在使用WPF,所以我使用的图像是BitmapImage对象,而不是Bitmap对象,这意味着我需要从BitmapImage对象开始,将其保存到内存流,创建一个新的Bitmap对象从内存流中,经过去偏移过程,将去偏移的图像保存到新的内存流,然后从所述内存流创建一个新的BitmapImage对象。不仅如此,歪斜也不是很好。
我正在尝试读取扫描到扫描仪中的纸张的OMR数据,因此我需要依赖于每次都处于相同坐标的特定OMR盒,因此需要可靠的偏斜校正。 / p>
所以我现在正在使用Aforge,我在.Net中找不到任何其他免费/开源库来进行图像校正,我发现的所有内容都是昂贵的或者是C / C ++。
我的问题是,是否有其他免费/开源库可以帮助.Net中的图像校正?如果是这样,他们叫什么,如果不是我应该如何处理这个问题呢?
编辑:例如,假设我有以下页面:
注意:这仅用于说明目的,但实际图像确实在页面的每个角落都有一个黑色矩形,这可能会有所帮助。
当我打印出来并将其扫描回扫描仪时,它看起来像这样:
我需要去掉这张图片,这样我的盒子每次都在同一个地方。在现实世界中,有很多盒子,它们较小且靠得很近,所以准确性很重要。
我目前采用的方法是大肆无效的痛苦:
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;
}
回想起来,还有几个问题:
答案 0 :(得分:6)
鉴于样本输入,很明显您不是在图像校正之后。这种操作不会纠正您的失真,而是需要执行透视变换。这可以在下图中清楚地看到。四个白色矩形代表四个黑盒子的边缘,黄色线条是连接黑盒子的结果。黄色四边形不是歪斜的红色(你想要达到的那个)。
所以,如果你能真正得到上面的数字,问题会变得更加简单。如果您没有四个角落盒,则需要其他四个参考点,因此它们对您有很大帮助。获得上面的图像后,您将知道四个黄色角,然后将它们映射到四个红色角。这是你需要做的透视转换,根据你的库,可能有一个现成的函数(至少有一个,检查你的问题的评论)。
有多种方法可以获得上面的图像,因此我将简单地描述一个相对简单的方法。首先,将灰度图像二值化。为此,我选择了一个简单的全局阈值100(您的图像在[0,255]范围内),它将框和其他细节保留在图像中(如图像周围的强线)。大于或等于100的强度设置为255,低于100的强度设置为0.但是,由于这是打印图像,因此框出现的暗度很可能会变化。所以你可能需要一个更好的方法,像形态渐变这样简单的方法可能会更好。第二步是消除不相关的细节。为此,请使用7x7的正方形进行形态学闭合(约为输入图像宽度和高度之间最小值的1%)。要获得方框的边框,请使用current_image - erosion(current_image)
中使用基本3x3方格的形态侵蚀。现在你有一个带有上面四个白色轮廓的图像(这假设除了盒子之外的所有东西都被消除了,我相信其他输入的简化)。要获得这些白色轮廓的像素,您可以执行连接组件标注。使用这4个组件,确定右上角,左上角,右下角和左下角。现在,您可以轻松找到所需的点以获取黄色矩形的角。所有这些操作都可以在AForge中使用,因此只需将以下代码转换为C#:
import sys
import numpy
from PIL import Image, ImageOps, ImageDraw
from scipy.ndimage import morphology, label
# Read input image and convert to grayscale (if it is not yet).
orig = Image.open(sys.argv[1])
img = ImageOps.grayscale(orig)
# Convert PIL image to numpy array (minor implementation detail).
im = numpy.array(img)
# Binarize.
im[im < 100] = 0
im[im >= 100] = 255
# Eliminate undesidered details.
im = morphology.grey_closing(im, (7, 7))
# Border of boxes.
im = im - morphology.grey_erosion(im, (3, 3))
# Find the boxes by labeling them as connected components.
lbl, amount = label(im)
box = []
for i in range(1, amount + 1):
py, px = numpy.nonzero(lbl == i) # Points in this connected component.
# Corners of the boxes.
box.append((px.min(), px.max(), py.min(), py.max()))
box = sorted(box)
# Now the first two elements in the box list contains the
# two left-most boxes, and the other two are the right-most
# boxes. It remains to stablish which ones are at top,
# and which at bottom.
top = []
bottom = []
for index in [0, 2]:
if box[index][2] > box[index+1][2]:
top.append(box[index + 1])
bottom.append(box[index])
else:
top.append(box[index])
bottom.append(box[index + 1])
# Pick the top left corner, top right corner,
# bottom right corner, and bottom left corner.
reference_corners = [
(top[0][0], top[0][2]), (top[1][1], top[1][2]),
(bottom[1][1], bottom[1][3]), (bottom[0][0], bottom[0][3])]
# Convert the image back to PIL (minor implementation detail).
img = Image.fromarray(im)
# Draw lines connecting the reference_corners for visualization purposes.
visual = img.convert('RGB')
draw = ImageDraw.Draw(visual)
draw.line(reference_corners + [reference_corners[0]], fill='yellow')
visual.save(sys.argv[2])
# Map the current quadrilateral to an axis-aligned rectangle.
min_x = min(x for x, y in reference_corners)
max_x = max(x for x, y in reference_corners)
min_y = min(y for x, y in reference_corners)
max_y = max(y for x, y in reference_corners)
# The red rectangle.
perfect_rect = [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
# Use these points to do the perspective transform.
print reference_corners
print perfect_rect
输入图像上面代码的最终输出是:
[(55, 30), (734, 26), (747, 1045), (41, 1036)]
[(41, 26), (747, 26), (747, 1045), (41, 1045)]
第一个点列表描述黄色矩形的四个角,第二个列与红色矩形相关。要进行透视变换,可以将AForge与ready函数一起使用。为了简单起见我使用了ImageMagick:
convert input.png -distort Perspective "55,30,41,26 734,26,747,26 747,1045,747,1045 41,1036,41,1045" result.png
它给出了你所追求的对齐方式(如前所示找到蓝线以更好地显示结果):
您可能会注意到左侧垂直蓝线并非完全笔直,实际上两个最左侧的框在x轴上未对齐1个像素。这可以通过透视变换期间使用的不同插值来校正。
答案 1 :(得分:1)
John Leptonica图书馆意味着非常快速和稳定 以下是有关如何从c#http://www.leptonica.com/vs2008doc/csharp-and-leptonlib.html调用它的链接。我不确定这是否是答案所以我刚刚添加了评论。
它有一个LeptonicaCLR.Utils.DeskewBinaryImage()来实际校正b&amp; w图像。
我不确定你试图处理的实际表格会有多好。
答案 2 :(得分:1)
约翰, 我也在想模板匹配可能有助于解决这个问题(如果Leptonica库不够好)。
Aforge.net内置模板匹配: http://www.aforgenet.com/framework/docs/html/17494328-ef0c-dc83-1bc3-907b7b75039f.htm
在我对此有限的了解中,您将拥有裁剪/定位标记的源图像,并使用扫描图像中的模板匹配找到它。然后,您可以裁剪图像以获得注册标记内部的子图像。对于上面提供的图像,我认为你可以假设一个相当小的初始偏斜,只在图像的裁剪区域上执行模板匹配,以减少总时间。
答案 3 :(得分:1)
对于那些以google结尾的用户来说,可以使用Magick.NET库轻松进行去偏斜。
安装其中一个nuget软件包,例如Magick.NET-Q16-AnyCPU。
用法:
folder = r'I:\PhD_2019\Spatial_Datasets\Baroon_Pocket_Dam_Catchment\Raster\Soil_Zonal_Stats'
filenames =
os.listdir(r'I:\PhD_2019\Spatial_Datasets\Baroon_Pocket_Dam_Catchment\Raster\Soil_Zonal_Stats')
for filename in filenames:
os.rename(os.path.join(folder, filename), os.path.join(folder, filename.replace("Z_C","")))