如何在C#项目中实现和执行OCR?

62

我已经搜索了一段时间,看到了一些OCR库的请求。我想知道如何在C#项目中实现最纯净、易于安装和使用的OCR库,并提供详细的安装信息。

如果可能的话,我只想像普通的DLL引用一样进行实现...

例子:

using org.pdfbox.pdmodel;
using org.pdfbox.util;

同时提供一小段OCR代码示例会更好,例如:

public string OCRFromBitmap(Bitmap Bmp)
{
    Bmp.Save(temppath, System.Drawing.Imaging.ImageFormat.Tiff);
    string OcrResult = Analyze(temppath);
    File.Delete(temppath);
    return OcrResult;
}

请注意,我对OCR项目不熟悉,请像向一个笨蛋一样给我答案。

编辑:我猜人们误解了我的请求。我想知道如何将这些开源OCR库实现到C#项目中以及如何使用它们。 给出的重复链接根本没有提供我所要求的答案。

5个回答

135
如果有人在研究这个问题,我尝试了不同的选项,以下方法可以产生非常好的结果。以下是获得工作示例的步骤:
  1. .NET Wrapper for tesseract添加到您的项目中。可以通过NuGet包Install-Package Tesseracthttps://github.com/charlesw/tesseract)添加。
  2. 转到官方Tesseract项目的Downloads部分(https://code.google.com/p/tesseract-ocr/ EDIT:现在位于此处:https://github.com/tesseract-ocr/langdata)。
  3. 下载首选语言数据,例如:tesseract-ocr-3.02.eng.tar.gz 英语语言数据,适用于Tesseract 3.02
  4. 在您的项目中创建tessdata目录,并将语言数据文件放入其中。
  5. 转到新添加的文件的属性并设置它们在构建时复制。
  6. 添加对System.Drawing的引用。
  7. 从.NET Wrapper存储库中,在Samples目录中将示例phototest.tif文件复制到您的项目目录中,并将其设置为在构建时复制。
  8. 在您的项目中创建以下两个文件(仅供参考):

Program.cs

using System;
using Tesseract;
using System.Diagnostics;

namespace ConsoleApplication
{
    class Program
    {
        public static void Main(string[] args)
        {
            var testImagePath = "./phototest.tif";
            if (args.Length > 0)
            {
                testImagePath = args[0];
            }

            try
            {
                var logger = new FormattedConsoleLogger();
                var resultPrinter = new ResultPrinter(logger);
                using (var engine = new TesseractEngine(@"./tessdata", "eng", EngineMode.Default))
                {
                    using (var img = Pix.LoadFromFile(testImagePath))
                    {
                        using (logger.Begin("Process image"))
                        {
                            var i = 1;
                            using (var page = engine.Process(img))
                            {
                                var text = page.GetText();
                                logger.Log("Text: {0}", text);
                                logger.Log("Mean confidence: {0}", page.GetMeanConfidence());

                                using (var iter = page.GetIterator())
                                {
                                    iter.Begin();
                                    do
                                    {
                                        if (i % 2 == 0)
                                        {
                                            using (logger.Begin("Line {0}", i))
                                            {
                                                do
                                                {
                                                    using (logger.Begin("Word Iteration"))
                                                    {
                                                        if (iter.IsAtBeginningOf(PageIteratorLevel.Block))
                                                        {
                                                            logger.Log("New block");
                                                        }
                                                        if (iter.IsAtBeginningOf(PageIteratorLevel.Para))
                                                        {
                                                            logger.Log("New paragraph");
                                                        }
                                                        if (iter.IsAtBeginningOf(PageIteratorLevel.TextLine))
                                                        {
                                                            logger.Log("New line");
                                                        }
                                                        logger.Log("word: " + iter.GetText(PageIteratorLevel.Word));
                                                    }
                                                } while (iter.Next(PageIteratorLevel.TextLine, PageIteratorLevel.Word));
                                            }
                                        }
                                        i++;
                                    } while (iter.Next(PageIteratorLevel.Para, PageIteratorLevel.TextLine));
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
                Console.WriteLine("Unexpected Error: " + e.Message);
                Console.WriteLine("Details: ");
                Console.WriteLine(e.ToString());
            }
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }



        private class ResultPrinter
        {
            readonly FormattedConsoleLogger logger;

            public ResultPrinter(FormattedConsoleLogger logger)
            {
                this.logger = logger;
            }

            public void Print(ResultIterator iter)
            {
                logger.Log("Is beginning of block: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Block));
                logger.Log("Is beginning of para: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Para));
                logger.Log("Is beginning of text line: {0}", iter.IsAtBeginningOf(PageIteratorLevel.TextLine));
                logger.Log("Is beginning of word: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Word));
                logger.Log("Is beginning of symbol: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Symbol));

                logger.Log("Block text: \"{0}\"", iter.GetText(PageIteratorLevel.Block));
                logger.Log("Para text: \"{0}\"", iter.GetText(PageIteratorLevel.Para));
                logger.Log("TextLine text: \"{0}\"", iter.GetText(PageIteratorLevel.TextLine));
                logger.Log("Word text: \"{0}\"", iter.GetText(PageIteratorLevel.Word));
                logger.Log("Symbol text: \"{0}\"", iter.GetText(PageIteratorLevel.Symbol));
            }
        }
    }
}

FormattedConsoleLogger.cs

using System;
using System.Collections.Generic;
using System.Text;
using Tesseract;

namespace ConsoleApplication
{
    public class FormattedConsoleLogger
    {
        const string Tab = "    ";
        private class Scope : DisposableBase
        {
            private int indentLevel;
            private string indent;
            private FormattedConsoleLogger container;

            public Scope(FormattedConsoleLogger container, int indentLevel)
            {
                this.container = container;
                this.indentLevel = indentLevel;
                StringBuilder indent = new StringBuilder();
                for (int i = 0; i < indentLevel; i++)
                {
                    indent.Append(Tab);
                }
                this.indent = indent.ToString();
            }

            public void Log(string format, object[] args)
            {
                var message = String.Format(format, args);
                StringBuilder indentedMessage = new StringBuilder(message.Length + indent.Length * 10);
                int i = 0;
                bool isNewLine = true;
                while (i < message.Length)
                {
                    if (message.Length > i && message[i] == '\r' && message[i + 1] == '\n')
                    {
                        indentedMessage.AppendLine();
                        isNewLine = true;
                        i += 2;
                    }
                    else if (message[i] == '\r' || message[i] == '\n')
                    {
                        indentedMessage.AppendLine();
                        isNewLine = true;
                        i++;
                    }
                    else
                    {
                        if (isNewLine)
                        {
                            indentedMessage.Append(indent);
                            isNewLine = false;
                        }
                        indentedMessage.Append(message[i]);
                        i++;
                    }
                }

                Console.WriteLine(indentedMessage.ToString());

            }

            public Scope Begin()
            {
                return new Scope(container, indentLevel + 1);
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    var scope = container.scopes.Pop();
                    if (scope != this)
                    {
                        throw new InvalidOperationException("Format scope removed out of order.");
                    }
                }
            }
        }

        private Stack<Scope> scopes = new Stack<Scope>();

        public IDisposable Begin(string title = "", params object[] args)
        {
            Log(title, args);
            Scope scope;
            if (scopes.Count == 0)
            {
                scope = new Scope(this, 1);
            }
            else
            {
                scope = ActiveScope.Begin();
            }
            scopes.Push(scope);
            return scope;
        }

        public void Log(string format, params object[] args)
        {
            if (scopes.Count > 0)
            {
                ActiveScope.Log(format, args);
            }
            else
            {
                Console.WriteLine(String.Format(format, args));
            }
        }

        private Scope ActiveScope
        {
            get
            {
                var top = scopes.Peek();
                if (top == null) throw new InvalidOperationException("No current scope");
                return top;
            }
        }
    }
}

4
我希望我可以投多次票,因为这是一个如此好的指南,可以让那个东西运行起来。 - BloodyRain2k
2
@BloodyRain2k,很高兴你觉得它有用。谢谢你的赞美之词。 - B.K.
2
我使用了您上面提到的链接。在 eng 文件夹中(https://github.com/tesseract-ocr/langdata/tree/master/eng),有一个文件缺失,即 eng.traineddata。请也添加这个文件。 - Mughees Musaddiq
2
@MugheesMusaddiq 他们经常更改文件,这就是为什么我不愿意放置任何链接的原因,因为它们不能保证在未来保持相同。这更多是作为入门指南,缺乏链接保证是我在此处粘贴了如此多的代码的原因。 - B.K.
2
可以在此处下载语言数据的旧版本:https://sourceforge.net/projects/tesseract-ocr-alt/files/(例如,因为现在NuGet包的版本是3.02,而上面链接的网站上唯一可用的语言数据是3.04;或者可以使用Wayback Machine) - mYnDstrEAm
显示剩余9条评论

12

这里有一个:(查看http://hongouru.blogspot.ie/2011/09/c-ocr-optical-character-recognition.html 或者 http://www.codeproject.com/Articles/41709/How-To-Use-Office-2007-OCR-Using-C 获得更多信息)

using MODI;
static void Main(string[] args)
{
    DocumentClass myDoc = new DocumentClass();
    myDoc.Create(@"theDocumentName.tiff"); //we work with the .tiff extension
    myDoc.OCR(MiLANGUAGES.miLANG_ENGLISH, true, true);

    foreach (Image anImage in myDoc.Images)
    {
        Console.WriteLine(anImage.Layout.Text); //here we cout to the console.
    }
}

1
我该如何获取MODI?我已经安装了Microsoft Office 2010和2013。 - mYnDstrEAm
我有MS Office,但是引用无法解析(它们有黄色警告三角形),因此项目无法构建。 - Ewan

7
我将使用TessNet2(一个C#封装器 - http://www.pixel-technology.com/freeware/tessnet2/)来使用Tesseract OCR引擎。
一些基本代码:
using tessnet2;

...

Bitmap image = new Bitmap(@"u:\user files\bwalker\2849257.tif");
            tessnet2.Tesseract ocr = new tessnet2.Tesseract();
            ocr.SetVariable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,$-/#&=()\"':?"); // Accepted characters
            ocr.Init(@"C:\Users\bwalker\Documents\Visual Studio 2010\Projects\tessnetWinForms\tessnetWinForms\bin\Release\", "eng", false); // Directory of your tessdata folder
            List<tessnet2.Word> result = ocr.DoOCR(image, System.Drawing.Rectangle.Empty);
            string Results = "";
            foreach (tessnet2.Word word in result)
            {
                Results += word.Confidence + ", " + word.Text + ", " + word.Left + ", " + word.Top + ", " + word.Bottom + ", " + word.Right + "\n";
            }

1
在您提供的链接中,有另一个名为“在此下载二进制文件”的链接,但它无法使用。实际上,这个链接出现在许多网站上,在任何一个网站上都无法使用。有没有人知道从哪里可以下载tessnet2.dll文件呢? - Ewan
2
我实际上在NuGet中找到了tessnet2,不确定为什么我没有先在那里查找。但是当我运行它时,它会在ocr.Init行停止。那个目录里面应该有特定的东西吗?tessnet2_32.dll在我的“tessdata”文件夹中,我的应用程序exe文件也在其中。你知道为什么它会停止吗?它根本就不做任何事情。 - Ewan

3
一些在线API效果不错:ocr.spaceGoogle Cloud Vision。只要您每月进行的OCR少于1000次,这两个API都是免费的。您可以拖放图像进行快速手动测试,以查看它们对您的图像的表现如何。
我发现使用OCR.space更容易(没有nuget库的麻烦),但是对于我的用途,Google Cloud Vision提供了比OCR.space略好的结果。
Google Cloud Vision示例:
GoogleCredential cred = GoogleCredential.FromJson(json);
Channel channel = new Channel(ImageAnnotatorClient.DefaultEndpoint.Host, ImageAnnotatorClient.DefaultEndpoint.Port, cred.ToChannelCredentials());
ImageAnnotatorClient client = ImageAnnotatorClient.Create(channel);
Image image = Image.FromStream(stream);

EntityAnnotation googleOcrText = client.DetectText(image).First();
Console.Write(googleOcrText.Description);

OCR.space 示例:

string uri = $"https://api.ocr.space/parse/imageurl?apikey=helloworld&url={imageUri}";
string responseString = WebUtilities.DoGetRequest(uri);
OcrSpaceResult result = JsonConvert.DeserializeObject<OcrSpaceResult>(responseString);
if ((!result.IsErroredOnProcessing) && !String.IsNullOrEmpty(result.ParsedResults[0].ParsedText))
  return result.ParsedResults[0].ParsedText;

1
什么是GoogleCredential?JSON变量又是什么? - asmgx

2

WinRT/UWP 有一个新的 API:OcrEngine.RecognizeAsync。它也可以在 WinForms 中使用:

...
//for AsBuffer
using System.Runtime.InteropServices.WindowsRuntime;
...

    private async Task<SoftwareBitmap> loadSoftwareBitmap(string fn)
    {
        var sf= await StorageFile.GetFileFromPathAsync(fn);
        SoftwareBitmap sb;
        using (IRandomAccessStream stream = await sf.OpenAsync(FileAccessMode.Read))
        {         
            BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
            sb = await decoder.GetSoftwareBitmapAsync();
        }
        return sb;
    }

async private void button5_Click(object sender, EventArgs e)
{
    OcrEngine ocrEngine = null;
    ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
    if (ocrEngine == null) return;

    var fn = @"1.png";            

    var outputBitmap =await loadSoftwareBitmap(fn);

    var ocrResult = await ocrEngine.RecognizeAsync(outputBitmap);                        
}

要在WinForms中使用WinRT/UWP API,请按照此处所述添加Nuget包“Microsoft.Windows.SDK.Contracts”(在此处测试了Win10 1803 SDK的版本为10.0.17134.100)。

编辑:以前的版本非常慢,大部分时间都用于图像和SoftwareBitmap之间的转换。新版本直接加载SoftwareBitmap,速度非常快。虽然它不能直接识别弯曲纸张上的字符(据我所知,许多其他框架也无法直接对弯曲纸张进行OCR),但结果非常好。


不支持 .Net Framework 4.8。 - ThexBasic
@ThexBasic 我已经检查了我的测试项目和真实项目,它们确实都是在VS2017上使用.NET Framework 4.8。那么你遇到的问题究竟是什么? - jw_
即使我添加了NuGet包,"using"仍然丢失并且无法解决。 - ThexBasic
@ThexBasic 请检查您的环境,使用 Microsoft.Windows.SDK.Contracts 的版本10.0.17134.100(最旧的版本)。当我尝试使用最新版本时,它甚至无法安装,这可能是您的原因。VS2017 15.9.6。我可以创建一个新的WinForm .NET Framework 4.8应用程序并安装该版本,然后复制上面的代码并成功运行。还要使用以下内容:using System.Runtime.InteropServices.WindowsRuntime; using Windows.Media.Ocr; using Windows.Graphics.Imaging; - jw_
从两个测试来看,这比Tesseract表现得更好,但是如果结合起来使用,效果会更好。唯一需要注意的是:它不能在Windows之外工作。 - Master DJon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接