使用.Net取消对图像的查看

使用.Net取消对图像的查看,.net,image-processing,.net,Image Processing,我一直在到处寻找一种可靠的方法来在.Net中对图像进行反视图,但运气不太好 此刻,我正在使用一个按钮。这是我在使用WPF时遇到的一个难题,因此我正在使用的图像是BitmapImage对象,而不是位图对象,这意味着我需要从BitmapImage对象开始,将其保存到内存流,从内存流创建新的位图对象,完成桌面查看过程,将桌面浏览的图像保存到新的内存流,然后从所述内存流创建新的BitmapImage对象。不仅如此,桌面设计也不是很好 我试图将扫描到扫描仪中的一张纸的OMR数据读取到扫描仪中,因此我需要依

我一直在到处寻找一种可靠的方法来在.Net中对图像进行反视图,但运气不太好

此刻,我正在使用一个按钮。这是我在使用WPF时遇到的一个难题,因此我正在使用的图像是BitmapImage对象,而不是位图对象,这意味着我需要从BitmapImage对象开始,将其保存到内存流,从内存流创建新的位图对象,完成桌面查看过程,将桌面浏览的图像保存到新的内存流,然后从所述内存流创建新的BitmapImage对象。不仅如此,桌面设计也不是很好

我试图将扫描到扫描仪中的一张纸的OMR数据读取到扫描仪中,因此我需要依赖于一个特定的OMR盒每次都处于相同的坐标,因此桌面布线需要可靠

因此,我现在正在使用一个RGE,我在.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;
}
回想起来,还有几个问题:

  • 我是否正确使用了一个按钮
  • Forge是执行此任务的最佳库吗
  • 如何改进我目前的方法以获得更准确的结果

  • John Leptonica图书馆意味着非常快速和稳定。
    这里有一个关于如何从c#调用它的链接。我不确定这是否是答案,所以我只是添加了一条评论

    它有一个LeptonicaCLR.Utils.DeskewBinaryImage()来实际对黑白图像进行反查看

    我不确定你要处理的表单的实际效果如何。

    John, 我还认为模板匹配也可能有助于解决这个问题(如果Leptonica库不够好的话)

    Aforge.net具有内置的模板匹配功能:

    在我有限的知识,这方面,你会有一个作物/登记标记的源图像,并找到它使用模板匹配扫描图像。然后,您可以裁剪图像以获得注册标记内零件的子图像。对于上面提供的图像,我认为您可以假设一个相当小的初始倾斜,只在图像的裁剪区域执行模板匹配,以减少总时间

    这里有一些关于这方面的讨论:

    给定示例输入,很明显您不是在进行图像桌面搜索。这种操作不会纠正您的失真,相反,您需要执行透视变换。这可以在下图中清楚地看到。四个白色矩形代表四个黑盒子的边缘,黄线是连接黑盒子的结果。黄色四边形不是倾斜的红色四边形(您希望实现的四边形)

    所以,如果你能得到上面的数字,问题就简单多了。如果您没有四个角盒,您将需要其他四个参考点,因此它们确实对您有很大帮助。在你得到上面的图像后,你知道四个黄色的角,然后你只需要把它们映射到四个红色的角。这是您需要进行的透视变换,根据您的库,可能有一个现成的函数(至少有一个,请检查您的问题的注释)

    有多种方法可以获得上面的图像,因此我将简单地描述一个相对简单的方法。首先,对灰度图像进行二值化。为此,我选择了一个简单的全局阈值100(您的图像在[0255]范围内),它将框和其他细节保留在图像中(如图像周围的强线)。高于或等于100的强度设置为255,低于100的强度设置为0。但是,由于这是一张打印图像,盒子的颜色很可能会有所不同。所以你可能需要一个更好的方法,像形态学梯度这样简单的方法可能会更好。第二步是消除不相关的细节。为此,使用7x7正方形(输入图像宽度和高度之间最小值的1%)执行形态学闭合。要获得框的边界,请使用基本的3x3正方形,使用形态腐蚀,如
    当前_图像-腐蚀(当前_图像)
    中所示。现在您有了一个具有四个白色轮廓的图像,如上所述(这是假设除方框外的所有内容都已删除,我相信这是对其他输入的简化)。要获得这些白色轮廓的像素,可以进行连接组件标记。使用这4个组件,确定右上角、左上角、右下角和左下角。现在,您可以轻松找到所需的点,以获得黄色矩形的角。所有这些操作在Forge中都是现成的,因此只需将以下代码翻译成C即可:

    第一个点列表描述黄色矩形的四个角,第二个点列表与红色矩形相关。要进行透视变换,可以使用带有ready函数的forge。为了简单起见,我使用了ImageMagick,如:

    convert input.png -distort Perspective "55,30,41,26 734,26,747,26 747,1045,747,1045 41,1036,41,1045" result.png
    
    这将给出您所追求的对齐方式(如前所示,找到蓝色线条以更好地显示结果):

    您可能会注意到,左侧垂直蓝线不是完全垂直的
    [(55, 30), (734, 26), (747, 1045), (41, 1036)]
    [(41, 26), (747, 26), (747, 1045), (41, 1045)]
    
    convert input.png -distort Perspective "55,30,41,26 734,26,747,26 747,1045,747,1045 41,1036,41,1045" result.png
    
    private byte[] DeskewImage(byte[] imageFileBytes)
    {
        var img = new MagickImage(imageFileBytes);
        // ImageMagick docs say 40% should work for most images
        img.Deskew(new Percentage(40d));
        return img.ToByteArray();
    }