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; } }