Microsoft OCRを用いてPDFからテキストを抽出する

土曜日 , 15, 1月 2022 1 Comment

コピー機などでPDF化された紙文書からテキストデータを抽出するため、PDFからテキストを抽出するライブラリを作成しました。
Windows 10に搭載されているMicrosoft OCRを使って文字認識する』で作成した画像からテキストを抽出するプログラムの応用になります。

PDF文書をいったん画像として展開したものに対しOCRをかけているため100%正確なテキストは抽出できませんが、取り込んだ文書の画像状態がよければ高品質のテキストを抽出することができます。

※2022/02/05 『GUI対応したPDFをページごとに画像変換(PNG/BMP/JPEG/GIF/TIFF形式)するツール [WPF]』で応用アプリケーションを掲載しました。

ソースコード

使用する際の注意事項

このプログラムはOCRを実現するためWinRT APIを使用しています。
NuGetなどでMicrosoft.Windows.SDK.Contractsをインストールしてください。

Microsoft.Windows.SDK.Contractsをインストールするためにプロジェクトの形式をPackageReferenceに移行する必要があるかも知れません。
移行の手順は『packages.config から PackageReference への移行』に詳しく記載されています。
移行が行えない環境の場合は、UwpDesktopなどWinRT呼び出しのために必要なパッケージをプロジェクトに追加することで解決できる可能性があります。

//
// Microsoft OCRを用いてPDFからテキストを取得するライブラリ
//
// This software is distributed under the license of NYSL.
// http://www.kmonos.net/nysl/
// 
//
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Windows.Data.Pdf;
using Windows.Graphics.Imaging;
using Windows.Media.Ocr;
using Windows.Storage.Streams;

namespace PDFOCR
{
    /// <summary>
    /// Microsoft OCRを用いてPDFからテキストを取得するライブラリ
    /// </summary>
    /// 
    /// <remarks>
    /// このライブラリはWinRT APIを使用しているため、NuGetでMicrosoft.Windows.SDK.Contractsを
    /// インストールしてください
    /// 
    /// https://www.nuget.org/packages/Microsoft.Windows.SDK.Contracts
    /// 
    /// </remarks>
    public static class OcrUtil
    {
        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        private static extern bool DeleteObject(IntPtr hObject);


        /// <summary>
        /// 指定パスのPDF文書をOCRにかけテキストを取得する
        /// </summary>
        /// <param name="pdfpath"></param>
        /// <returns></returns>
        public static async Task<string> GetOcrTextAsync(string pdfpath)
        {
            return await GetOcrTextAsync(System.IO.File.ReadAllBytes(pdfpath));
        }

        /// <summary>
        /// byte型の配列に格納したPDF文書をOCRにかけテキストを取得する
        /// </summary>
        /// <param name="ba"></param>
        /// <returns></returns>
        public static async Task<string> GetOcrTextAsync(byte[] ba)
        {
            // OCRエンジンを生成
            var engine = OcrEngine.TryCreateFromUserProfileLanguages();

            var sb = new StringBuilder();

            using (var instream = new MemoryStream(ba))
            using (var inrastream = instream.AsRandomAccessStream())
            {
                // PDFを読み込み、ページごとに画像に変換しOCRにかける
                // 変数pdfdocは処理のあいだ開いていないとビットマップへのレンダリングに失敗する
                var pdfdoc = await PdfDocument.LoadFromStreamAsync(inrastream);

                for (uint pageIndex = 0; pageIndex < pdfdoc.PageCount; pageIndex++)
                {
                    using (var pdfpage = pdfdoc.GetPage(pageIndex))
                    using (var outstream = new MemoryStream())
                    using (var outratream = outstream.AsRandomAccessStream())
                    {
                        // ページの内容をBitmapにレンダリング
                        // 解像度などはデフォルトに任せる
                        var renderOptions = new Windows.Data.Pdf.PdfPageRenderOptions();
                        await pdfpage.RenderToStreamAsync(outratream, renderOptions);

                        // Bitmapの内容をBitmapSourceに変換しOCRにかける
                        using (var image = System.Drawing.Image.FromStream(outstream))
                        {
                            var bmpsource = ConvertToBitmapSource(image as Bitmap);
                            sb.Append(await RecognizeFromBitmapAsync(engine, bmpsource));
                        }
                    }
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// BitmapをBitmapSourceに変換する
        /// </summary>
        /// <param name="bmp"></param>
        /// <returns></returns>
        private static BitmapSource ConvertToBitmapSource(Bitmap bmp)
        {
            using (var dest = new Bitmap(bmp.Width, bmp.Height))
            using (var g = Graphics.FromImage(dest))
            {
                g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
                var hbitmap = dest.GetHbitmap();
                BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty,
                    BitmapSizeOptions.FromWidthAndHeight(bmp.Width, bmp.Height));
                DeleteObject(hbitmap);

                return bitmapSource;
            }
        }

        /// <summary>
        /// BitmapSourceの内容をMicrosoft OCRで文字認識させる
        /// </summary>
        /// <param name="bmpsource"></param>
        /// <returns></returns>
        private static async Task<string> RecognizeFromBitmapAsync(OcrEngine engine, BitmapSource bmpsource)
        {
            var encoder = new BmpBitmapEncoder();
            encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmpsource));

            using (var stream = new MemoryStream())
            {
                encoder.Save(stream);

                // BitmapSourceの内容をSoftwareBitmapに変換
                using (var softwareBitmap = await LoadImageFromByteArrayAsync(stream.ToArray()))
                {
                    var result = await engine.RecognizeAsync(softwareBitmap);
                    var lines = new List<string>();
                    result.Lines.ToList().ForEach(line => lines.Add(line.Text));

                    return string.Join("\n", lines);
                }
            }
        }

        /// <summary>
        /// byte型の配列からSoftwareBitmapを取得する
        /// </summary>
        /// <param name="ba"></param>
        /// <returns></returns>
        private static async Task<SoftwareBitmap> LoadImageFromByteArrayAsync(byte[] ba)
        {
            using (var imrastream = new InMemoryRandomAccessStream())
            using (var outstream = imrastream.GetOutputStreamAt(0))
            using (var writer = new DataWriter(outstream))
            {
                writer.WriteBytes(ba);
                await writer.StoreAsync();
                if (!await outstream.FlushAsync()) throw new InvalidOperationException();

                var decoder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(imrastream);

                return await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
            }
        }
    }
}

One thought on “ : Microsoft OCRを用いてPDFからテキストを抽出する”
  • […] プログラムの流れとしては、以前記事にした『Microsoft OCRを用いてPDFからテキストを抽出する』とほぼ同じで、OCRエンジンに流していた画像を解像度指定でPNG形式に変換できるようにしました。 […]

  • Please give us your valuable comment

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です

    このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください