Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 确定图像倾斜的有效方法_C#_Image Processing - Fatal编程技术网

C# 确定图像倾斜的有效方法

C# 确定图像倾斜的有效方法,c#,image-processing,C#,Image Processing,我正试图编写一个程序,以编程方式确定任意图像中的倾斜或旋转角度 图像具有以下属性: 由浅色背景上的深色文本组成 偶尔包含仅以90度角相交的水平线或垂直线 在-45和45度之间倾斜 请参见作为参考(其已倾斜2.8度) 到目前为止,我已经想出了这个策略:从左到右画一条路线,总是选择最近的白色像素。据推测,从左到右的路径更倾向于沿着图像倾斜方向沿着文本行之间的路径 这是我的密码: private bool IsWhite(Color c) { return c.GetBrightness() &g

我正试图编写一个程序,以编程方式确定任意图像中的倾斜或旋转角度

图像具有以下属性:

  • 由浅色背景上的深色文本组成
  • 偶尔包含仅以90度角相交的水平线或垂直线
  • 在-45和45度之间倾斜
  • 请参见作为参考(其已倾斜2.8度)
到目前为止,我已经想出了这个策略:从左到右画一条路线,总是选择最近的白色像素。据推测,从左到右的路径更倾向于沿着图像倾斜方向沿着文本行之间的路径

这是我的密码:

private bool IsWhite(Color c) { return c.GetBrightness() >= 0.5 || c == Color.Transparent; }

private bool IsBlack(Color c) { return !IsWhite(c); }

private double ToDegrees(decimal slope) { return (180.0 / Math.PI) * Math.Atan(Convert.ToDouble(slope)); }

private void GetSkew(Bitmap image, out double minSkew, out double maxSkew)
{
    decimal minSlope = 0.0M;
    decimal maxSlope = 0.0M;
    for (int start_y = 0; start_y < image.Height; start_y++)
    {
        int end_y = start_y;
        for (int x = 1; x < image.Width; x++)
        {
            int above_y = Math.Max(end_y - 1, 0);
            int below_y = Math.Min(end_y + 1, image.Height - 1);

            Color center = image.GetPixel(x, end_y);
            Color above = image.GetPixel(x, above_y);
            Color below = image.GetPixel(x, below_y);

            if (IsWhite(center)) { /* no change to end_y */ }
            else if (IsWhite(above) && IsBlack(below)) { end_y = above_y; }
            else if (IsBlack(above) && IsWhite(below)) { end_y = below_y; }
        }

        decimal slope = (Convert.ToDecimal(start_y) - Convert.ToDecimal(end_y)) / Convert.ToDecimal(image.Width);
        minSlope = Math.Min(minSlope, slope);
        maxSlope = Math.Max(maxSlope, slope);
    }

    minSkew = ToDegrees(minSlope);
    maxSkew = ToDegrees(maxSlope);
}
private bool IsWhite(Color c){return c.GetBrightness()>=0.5 | | c==Color.Transparent;}
私有布尔是黑色(c色){返回!是白色(c);}
私有双ToDegrees(十进制斜率){return(180.0/Math.PI)*Math.Atan(Convert.ToDouble(slope));}
私有void GetSkew(位图图像,输出双明斯克,输出双maxSkew)
{
小坡度=0.0M;
十进制最大坡度=0.0M;
对于(int start_y=0;start_y
这在某些图像上效果很好,在其他图像上效果不太好,而且速度很慢


是否有更有效、更可靠的方法来确定图像的倾斜?

如果文本是左(右)对齐的,您可以通过测量图像左(右)边缘与两个随机位置的第一个暗像素之间的距离来确定斜率,并从中计算斜率。额外的测量会在花费额外时间的同时降低误差。

GetPixel速度较慢。使用列出的方法,您可以获得一个数量级的速度。

首先,我必须说我喜欢这个想法。但我以前从来没有这样做过,我不知道该怎么做才能提高可靠性。我能想到的第一件事就是抛弃统计异常。如果坡度突然急剧变化,则您知道您发现图像的白色部分倾斜到边缘倾斜(并非有意双关语)您的结果。所以你想把那些东西扔掉

但是从性能的角度来看,您可以进行一些优化,这些优化可能会累加起来

也就是说,我要将您的内部循环中的这段代码更改为:

Color center = image.GetPixel(x, end_y);
Color above = image.GetPixel(x, above_y);
Color below = image.GetPixel(x, below_y);

if (IsWhite(center)) { /* no change to end_y */ }
else if (IsWhite(above) && IsBlack(below)) { end_y = above_y; }
else if (IsBlack(above) && IsWhite(below)) { end_y = below_y; }
为此:

Color center = image.GetPixel(x, end_y);

if (IsWhite(center)) { /* no change to end_y */ }
else
{
    Color above = image.GetPixel(x, above_y);
    Color below = image.GetPixel(x, below_y);
    if (IsWhite(above) && IsBlack(below)) { end_y = above_y; }
    else if (IsBlack(above) && IsWhite(below)) { end_y = below_y; }
}
这是相同的效果,但应该大大减少调用GetPixel的次数

还考虑在疯狂开始之前把不改变的值变成变量。像图像、高度和图像、宽度这样的东西每次你打电话时都会有一点开销。因此,在循环开始之前,将这些值存储在您自己的变量中。在处理嵌套循环时,我总是告诉自己,要优化最内部循环中的所有内容,而牺牲其他所有内容


还有。。。正如Vinko Vrsalovic所建议的,你可以看看他的GetPixel替代方案,以获得速度上的又一次提升。

乍一看,你的代码看起来过于幼稚。 这就解释了为什么它不总是起作用

我喜欢Steve Wortham建议的方法, 但是如果你有背景图片,它可能会遇到问题

另一种通常有助于处理图像的方法是首先模糊图像。 如果对示例图像进行足够的模糊,则每行文本都将结束 像一条模糊光滑的线。然后应用某种算法对 基本上做回归分析。有很多方法可以做 网络上有很多例子

边缘检测可能是有用的,或者它可能会导致更多值得考虑的问题

顺便说一下,如果您足够努力地搜索代码,高斯模糊可以非常有效地实现。否则,我肯定会有很多图书馆。 最近没有做太多,所以手头上没有任何链接。 但是搜索图像处理库会得到很好的结果


我假设您正在享受解决这个问题的乐趣,所以这里的实际实现细节并不多。

我对代码做了一些修改,它当然运行得更快,但不是很准确

我做了以下改进:

  • 使用,我避免使用GetPixel,而是直接使用字节,现在代码以我需要的速度运行

  • 我最初的代码只是简单地使用了“IsBlack”和“IsWhite”,但这不够精细。原始代码通过图像跟踪以下路径:

    请注意,文本中有许多路径。通过将“我的中心”、“上方”和“下方”路径与实际亮度值进行比较,并选择最亮的像素。基本上,我将位图视为高度贴图,从左到右的路径遵循图像的轮廓,从而得到更好的路径:

    正如所建议的,高斯模糊平滑了高度贴图,我得到了更好的结果:

    由于这只是原型代码,我使用GIMP模糊了图像,我没有编写自己的模糊函数

    对于贪婪算法来说,选择的路径非常好

  • 作为
    private double ToDegrees(double slope) { return (180.0 / Math.PI) * Math.Atan(slope); }
    
    private double GetSkew(Bitmap image)
    {
        BrightnessWrapper wrapper = new BrightnessWrapper(image);
    
        LinkedList<double> slopes = new LinkedList<double>();
    
        for (int y = 0; y < wrapper.Height; y++)
        {
            int endY = y;
    
            long sumOfX = 0;
            long sumOfY = y;
            long sumOfXY = 0;
            long sumOfXX = 0;
            int itemsInSet = 1;
            for (int x = 1; x < wrapper.Width; x++)
            {
                int aboveY = endY - 1;
                int belowY = endY + 1;
    
                if (aboveY < 0 || belowY >= wrapper.Height)
                {
                    break;
                }
    
                int center = wrapper.GetBrightness(x, endY);
                int above = wrapper.GetBrightness(x, aboveY);
                int below = wrapper.GetBrightness(x, belowY);
    
                if (center >= above && center >= below) { /* no change to endY */ }
                else if (above >= center && above >= below) { endY = aboveY; }
                else if (below >= center && below >= above) { endY = belowY; }
    
                itemsInSet++;
                sumOfX += x;
                sumOfY += endY;
                sumOfXX += (x * x);
                sumOfXY += (x * endY);
            }
    
            // least squares slope = (NΣ(XY) - (ΣX)(ΣY)) / (NΣ(X^2) - (ΣX)^2), where N = elements in set
            if (itemsInSet > image.Width / 2) // path covers at least half of the image
            {
                decimal sumOfX_d = Convert.ToDecimal(sumOfX);
                decimal sumOfY_d = Convert.ToDecimal(sumOfY);
                decimal sumOfXY_d = Convert.ToDecimal(sumOfXY);
                decimal sumOfXX_d = Convert.ToDecimal(sumOfXX);
                decimal itemsInSet_d = Convert.ToDecimal(itemsInSet);
                decimal slope =
                    ((itemsInSet_d * sumOfXY) - (sumOfX_d * sumOfY_d))
                    /
                    ((itemsInSet_d * sumOfXX_d) - (sumOfX_d * sumOfX_d));
    
                slopes.AddLast(Convert.ToDouble(slope));
            }
        }
    
        double mean = slopes.Average();
        double sumOfSquares = slopes.Sum(d => Math.Pow(d - mean, 2));
        double stddev = Math.Sqrt(sumOfSquares / (slopes.Count - 1));
    
        // select items within 1 standard deviation of the mean
        var testSample = slopes.Where(x => Math.Abs(x - mean) <= stddev);
    
        return ToDegrees(testSample.Average());
    }
    
    class BrightnessWrapper
    {
        byte[] rgbValues;
        int stride;
        public int Height { get; private set; }
        public int Width { get; private set; }
    
        public BrightnessWrapper(Bitmap bmp)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    
            System.Drawing.Imaging.BitmapData bmpData =
                bmp.LockBits(rect,
                    System.Drawing.Imaging.ImageLockMode.ReadOnly,
                    bmp.PixelFormat);
    
            IntPtr ptr = bmpData.Scan0;
    
            int bytes = bmpData.Stride * bmp.Height;
            this.rgbValues = new byte[bytes];
    
            System.Runtime.InteropServices.Marshal.Copy(ptr,
                           rgbValues, 0, bytes);
    
            this.Height = bmp.Height;
            this.Width = bmp.Width;
            this.stride = bmpData.Stride;
        }
    
        public int GetBrightness(int x, int y)
        {
            int position = (y * this.stride) + (x * 3);
            int b = rgbValues[position];
            int g = rgbValues[position + 1];
            int r = rgbValues[position + 2];
            return (r + r + b + g + g + g) / 6;
        }
    }