C#からTesseract OCRを扱うサンプルコード

コンピュータ

NuGet パッケージ追加

dotnet add package Tesseract

tessdata の取得

GitHubのリポジトリ
https://github.com/tesseract-ocr/tessdata_best

jpn.traineddata
jpn_vert.traineddata
を ./tessdata フォルダに配置します。

コンソールサンプル

画像ファイルから文字列を抽出

Program.cs

using System;
using Tesseract;

namespace TesseractDemo;
class Program
{
    static void Main()
    {
        // tessdata フォルダへのパス
        var tessDataPath = @"./tessdata";

        using var engine = new TesseractEngine(
            tessDataPath,
            "jpn",                // 日本語
            EngineMode.Default);

        using var img = Pix.LoadFromFile("sample.png");
        using var page = engine.Process(img);

        string text = page.GetText();

        Console.WriteLine("=== OCR Result ===");
        Console.WriteLine(text);

        Console.WriteLine($"Confidence: {page.GetMeanConfidence():P}");
    }
}
/*
テスト画像:

https://maywork.net/wp/wp-content/uploads/2018/10/logo.png
sample.pngというファイル名でカレントディレクトリに保存


実行例:


dotnet run
=== OCR Result ===
球 惑 堂 本 舗
創業 平成 参 拾 年

Confidence: 82.00%

*/

単語ごとに文字と座標を取得する

Program.cs

using System;
using Tesseract;

namespace TesseractDemo;
class Program
{
    static void Main()
    {
        // tessdata フォルダへのパス
        var tessDataPath = @"./tessdata";

        using var engine = new TesseractEngine(
            tessDataPath,
            "jpn",                // 日本語
            EngineMode.Default);

        using var img = Pix.LoadFromFile("sample.png");
        using var page = engine.Process(img);

		// イテレータを作成(PageIteratorLevelをWordにすると単語単位で取得可能)
		using (var iter = page.GetIterator())
		{
			iter.Begin();

			do
			{
				// テキストの取得
				string label = iter.GetText(PageIteratorLevel.Word);
				
				// 座標(矩形)の取得
				if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out Rect bounds))
				{
					Console.WriteLine($"Text: {label}");
					Console.WriteLine($"  Location: X={bounds.X1}, Y={bounds.Y1}, Width={bounds.Width}, Height={bounds.Height}");
					Console.WriteLine($"  Confidence: {iter.GetConfidence(PageIteratorLevel.Word):P}");
					Console.WriteLine("--------------------------");
				}

			} while (iter.Next(PageIteratorLevel.Word)); // 次の単語へ
		}
    }
}
/*
テスト画像:

https://maywork.net/wp/wp-content/uploads/2018/10/logo.png
sample.pngというファイル名でカレントディレクトリに保存


実行例:


dotnet run

Text: 球
  Location: X=18, Y=28, Width=473, Height=89
  Confidence: 8,350.87%
--------------------------
Text: 惑
  Location: X=162, Y=24, Width=110, Height=125
  Confidence: 9,179.91%
--------------------------
Text: 堂
  Location: X=271, Y=24, Width=87, Height=125
  Confidence: 9,291.83%
--------------------------
Text: 本
  Location: X=357, Y=24, Width=79, Height=125
  Confidence: 9,297.00%
--------------------------
Text: 舗
  Location: X=435, Y=24, Width=56, Height=125
  Confidence: 967.23%
--------------------------
Text: 創業
  Location: X=90, Y=143, Width=122, Height=45
  Confidence: 9,686.70%
--------------------------
Text: 平成
  Location: X=235, Y=144, Width=73, Height=44
  Confidence: 9,321.76%
--------------------------
Text: 参
  Location: X=331, Y=144, Width=23, Height=43
  Confidence: 9,151.46%
--------------------------
Text: 拾
  Location: X=354, Y=139, Width=47, Height=67
  Confidence: 8,152.60%
--------------------------
Text: 年
  Location: X=400, Y=144, Width=23, Height=44
  Confidence: 9,291.30%
--------------------------
*/

PageIteratorLevel.Wordを指定しているので「創業」や「平成」が単語として認識しています。

他の種類としては

Block … 文章の塊
TextLine … 行単位
Symbol … 1文字づつ

などがあります。Symbolを試した見たところ、「平成」が「平」と「成」で別々に認識していました。

WPFでGUIを作る

using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media;
using Tesseract;
using System.Windows.Controls;

namespace OcrViewer;

public partial class MainWindow : Window
{
    private BitmapImage? _currentBitmap;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Drop(object sender, DragEventArgs e)
    {
        if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            return;

        var files = (string[])e.Data.GetData(DataFormats.FileDrop);
        var path = files[0];

        ShowImage(path);
        RunOcrPerCharacter(path);
    }

    private void ShowImage(string path)
    {
        var bmp = new BitmapImage(new Uri(path));
        _currentBitmap = bmp;

        MainImage.Source = bmp;

        RootCanvas.Width = bmp.PixelWidth;
        RootCanvas.Height = bmp.PixelHeight;

        MainImage.Width = bmp.PixelWidth;
        MainImage.Height = bmp.PixelHeight;
    }

    private void RunOcrPerCharacter(string imagePath)
    {
        RootCanvas.Children.Clear();
        RootCanvas.Children.Add(MainImage);

        string tessPath = System.IO.Path.Combine(
            AppContext.BaseDirectory,
            "tessdata");

        using var engine = new TesseractEngine(tessPath, "jpn", EngineMode.Default);

        // 文字単位認識向け
        engine.DefaultPageSegMode = PageSegMode.SingleBlock;

        using var img = Pix.LoadFromFile(imagePath);
        using var page = engine.Process(img);

        using var iter = page.GetIterator();
        iter.Begin();

        do
        {
            string? text = iter.GetText(PageIteratorLevel.Symbol);

            if (string.IsNullOrWhiteSpace(text))
                continue;

            if (iter.TryGetBoundingBox(PageIteratorLevel.Symbol, out Tesseract.Rect bounds))
            {
                DrawRectangle(bounds, text);
            }

        } while (iter.Next(PageIteratorLevel.Symbol));
    }

    private void DrawRectangle(Tesseract.Rect bounds, string text)
    {
        var rect = new Rectangle
        {
            Width = bounds.Width,
            Height = bounds.Height,
            Stroke = Brushes.Red,
            StrokeThickness = 1
        };

        Canvas.SetLeft(rect, bounds.X1);
        Canvas.SetTop(rect, bounds.Y1);

        RootCanvas.Children.Add(rect);

        // 文字も重ねて表示(デバッグ用)
        var label = new System.Windows.Controls.TextBlock
        {
            Text = text,
            Foreground = Brushes.Blue,
            FontSize = 24
        };

        Canvas.SetLeft(label, bounds.X1);
        Canvas.SetTop(label, bounds.Y1 - 28);

        RootCanvas.Children.Add(label);
    }
}
<Window x:Class="OcrViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OcrViewer"
        mc:Ignorable="d"
        Title="OCR Viewer" Height="800" Width="1000"
        AllowDrop="True"
        Drop="Window_Drop">

    <Grid>
        <ScrollViewer HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">

            <Canvas x:Name="RootCanvas" Background="Black">

                <Image x:Name="MainImage" Stretch="None" />

            </Canvas>

        </ScrollViewer>
    </Grid>
</Window>

実行結果:

コードの書き方が悪いのか、盛大にズレていますね。

コメント