吹き出しの縦書き文字列をOCRで文字認識してみるスクリプト

コンピュータ

最近ゆっくりMovieMaker3というソフトでいろいろな文書をしゃべらせているのですが、喋らせたいテキストが画像ファイル上の吹き出しだったりすると手入力するわけです。吹き出しが沢山あると大変です。
吹き出し内の文字をマウスで範囲選択してOCR出来ないかと考えスクリプトを作りました。

OCR部分はTesseract-OCRを使っていますのでインストールしスクリプト内のパスを環境に合わせて修正してください。

<#
.SYNOPSIS
概要
#>
using namespace System.Windows.Forms
using namespace System.Drawing
using namespace System.IO


$ErrorActionPreference = "STOP"

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.IO


# OCRの実行
function Exec-OCR
{
    param (
        [string]$in
    )

    $tesseract_exe = Join-Path $env:ProgramFiles '\Tesseract-OCR\tesseract.exe'
    $scriptPath = $PSCommandPath

    # テンポラリファイル
    $TmpFile = New-TemporaryFile
    $out = $TmpFile.FullName

    $sp = (Get-Item -LiteralPath $scriptPath)
    # 標準出力のパス
    $log_path = Join-Path $sp.DirectoryName ($sp.BaseName + ".log")
    # 標準エラーのパス
    $err_path = Join-Path $sp.DirectoryName ($sp.BaseName + ".err")

    


    # -FilePath 実行ファイルのパス
    # -ArgumentList 実行ファイルの引数
    # -Wait コマンドの終了をまつ。
    # -PassThru 結果をオブジェクトで返す。
    # -RedirectStandardOutput 標準出力
    # -RedirectStandardError 標準エラー
    # -NoNewWindow ウィンドウを新たに開かない
    $proc = Start-Process -FilePath $tesseract_exe "${in} ${out} -l jpn_vert" -Wait -PassThru -RedirectStandardOutput $log_path -RedirectStandardError $err_path -NoNewWindow

    $result = ""
    switch ($proc.ExitCode) {
        0 {
            # echo "成功"
            $result = Get-Content -Encoding UTF8 ($out+".txt")
        }
        default {
            # echo "失敗"
        }
    }


    # テンポラリファイルを削除
    rm $TmpFile
    rm ($out+".txt")

    return $result
}

function next_file($path)
{
    $pos = $null

    $files = Get-ChildItem (Get-Item $path).Directory -Filter "*.png" -File
    for($i = 0; $i -lt $files.Length; $i++)
    {
        if ($files[$i].FullName -eq $path)
        {
            $pos = $i
        }
    }
    
    if (($files.Lengt-1) -eq $pos)
    {
        return $null
    } else {
        return $files[$pos+1].FullName
    }
}

function prev_file($path)
{
    $pos = $null

    $files = Get-ChildItem (Get-Item $path).Directory -Filter "*.png" -File
    for($i = 0; $i -lt $files.Length; $i++)
    {
        if ($files[$i].FullName -eq $path)
        {
            $pos = $i
        }
    }
    
    if (0 -eq $pos)
    {
        return $null
    } else {
        return $files[$pos-1].FullName
    }
}


# ピクチャボックス1
class PictureBox1 : PictureBox
{
    [string]$img_path

    [MouseEventArgs]$sPos
    [MouseEventArgs]$ePos

    # コンストラクタ
    PictureBox1()
    {
        $this.img_path = ""

        # イベントの登録
        $this.Add_MouseDown({$this.PictureBox1_MouseDown($this, $_)})
        $this.Add_MouseMove({$this.PictureBox1_MouseMove($this, $_)})
        $this.Add_MouseUp({$this.PictureBox1_MouseUp($this, $_)})
        $this.Add_MouseWheel({$this.PictureBox1_MouseWheel($this, $_)})
    }

    # MouseDownイベント
    PictureBox1_MouseDown($sender, $e)
    {
        if ($e.Button -eq [MouseButtons]::Left)
        {
            $this.sPos = $e
            $this.ePos = $e
        }
    }
    # MouseDownイベント
    PictureBox1_MouseMove($sender, $e)
    {
        if ($e.Button -eq [MouseButtons]::Left)
        {
            $g = $this.CreateGraphics()
            $BPen = [Pen]::new([Color]::Black, 1)
            $BPen.DashStyle = [Drawing2D.DashStyle]::Dash

            $this.Refresh()
            $g.DrawRectangle($BPen, $this.sPos.X, $this.sPos.Y, $this.ePos.X - $this.sPos.X, $this.ePos.Y - $this.sPos.Y)
            $this.ePos = $e
            $g.Dispose()
        }
    }
    # MouseUpイベント
    PictureBox1_MouseUp($sender, $e)
    {
        if ($e.Button -eq [MouseButtons]::Left)
        {
            $pic2 = $this.Parent.pic2


            $this.Refresh()
            $g = $this.CreateGraphics()
            $BPen = [Pen]::new([Color]::Black, 1)
            $BPen.DashStyle = [Drawing2D.DashStyle]::Dash

            $this.Refresh()


            $g.DrawRectangle($BPen, $this.sPos.X, $this.sPos.Y, ($this.ePos.X - $this.sPos.X), ($this.ePos.Y - $this.sPos.Y))
            $g.Dispose()

            Write-Host ("倍率計算:pic1 w:{0} h:{1}" -f $this.Width, $this.Height)
            if ($this.Image.Width -lt $this.Image.Height)
            {
                $rate = [double]($this.Image.Height / $this.Height)
                Write-Host ("倍率{0} h pic1:{1} img:{2}" -f $rate, $this.Height, $this.Image.Height)

                $xm = [int](($this.Width - [int]($this.Image.Width / $rate)) / 2)
                $ym = 0
            } else {
                $rate = [double]($this.Image.Width / $this.Width)                
                Write-Host ("倍率{0} w pic1:{1} img:{2}" -f $rate, $this.Width, $this.Image.Width)

                $xm = 0
                $ym = [int](($this.Height - [int]($this.Image.Height / $rate)) / 2)
            }
            Write-Host ("xm:{0} ym:{1}" -f $xm, $ym)
            $x = [int](($this.sPos.X - $xm) * $rate) 
            $y = [int](($this.sPos.Y - $ym) * $rate) 
            $w = [int](($this.ePos.X - $this.sPos.X) * $rate)
            $h = [int](($this.ePos.Y - $this.sPos.Y) * $rate)

            $rect = [Rectangle]::new($x, $y, $w, $h)

            if (($w -lt 2) -Or ($h -lt 2))
            {
                return
            }

            $bmp = [Bitmap]::new($this.Image)
            $pic2.Image = [Bitmap]::new($w, $h)
            $g2 = [Graphics]::FromImage($pic2.Image)

            $g2.DrawImage($bmp, 0, 0, $rect, [GraphicsUnit]::Pixel)

            $bmp.Dispose()
            $g2.Dispose()

            # 画像保存用テンポラリファイルの作成
            $temp_file = New-TemporaryFile
            try {
                if ($pic2.Image -ne $null)
                {
                    $pic2.Image.Save($temp_file, [Imaging.ImageFormat]::Png)
                    $txt1 = $this.Parent.txt1
                    $t = Exec-OCR $temp_file

                    if ($t -is [System.Object[]])
                    {
                        $t = $t -join ""
                    }

                    $t = $t -replace "\s+", ""
                    
                    $chars = [System.IO.Path]::GetInvalidFileNameChars()
                    foreach( $ch In $chars)
                    {
                        $t = $t.Replace($ch, "_")
                    }


                    Write-Host ("<{0}>" -f $t)

                    $txt1.Text = $t
                }
            } finally {
                rm $temp_file -force | Out-Null
            }

        }
    }
    # MouseWheellイベント
    PictureBox1_MouseWheel($sender, $e)
    {
        $file = $null

        if ($e.Delta -lt 0)
        {
            # 次へ
            $file = next_file $this.img_path
        }
        if ($e.Delta -gt 0)
        {
            # 前へ
            $file = prev_file $this.img_path
        }
        if ($file)
        {
            Write-Host $file
            $this.img_path = $file
            $this.Parent.Text = $file
                
            if ($fs = [FileStream]::new($file, [FileMode]::Open,[FileAccess]::Read)) {
                try {
                        
                    if ($this.Image) { $this.Image.Dispose() }

                    $this.Image = [System.Drawing.Image]::FromStream($fs)

                } finally {
                    $fs.Close()
                }
            }
        }
    }

}

# ピクチャボックス2
class PictureBox2 : PictureBox
{
    # コンストラクタ
    PictureBox2()
    {
    }
}

# メインフォーム
class Form1 : Form
{
    [PictureBox1]$pic1 # ピクチャボックス左
    [PictureBox2]$pic2 # ピクチャボックス右
    [TextBox]$txt1 # テキストボックス
    [Button]$btn1 # ボタン

    [string]$out_dir # 出力先のディレクトリ

    # コンストラクタ
    Form1()
    {
        
        # コントロールの生成
        $this.pic1 = [PictureBox1]::new() | % {
            $_.Name = "PIC1"
            $_.BorderStyle = "FixedSingle"
            $_.SizeMode = "Zoom"

            $_.AllowDrop = $true
            $_.Add_DragEnter({$_.Effect = "ALL"})
            $_.Add_DragDrop({
                $file = @($_.Data.GetData("FileDrop"))[0]

                $this.img_path = $file
                $this.Parent.Text = $file
                
                if ($fs = [FileStream]::new($file, [FileMode]::Open,[FileAccess]::Read)) {
                    try {
                        
                        if ($this.Image) { $this.Image.Dispose() }

                        $this.Image = [System.Drawing.Image]::FromStream($fs)

                    } finally {
                        $fs.Close()
                    }
                }

            })



            $_
        }
        $this.pic2 = [PictureBox2]::new() | % {
            $_.Name = "PIC2"
            $_.BorderStyle = "FixedSingle"
            $_.SizeMode = "Zoom"
            $_
        }
        $this.txt1 = [TextBox]::new() | % {
            $_.Name = "TXT1"
            $_
        }

        # イベントの登録
        $this.Add_Load({$this.Form1_Load($this, $_)})
        $this.Add_Resize({$this.Form1_Resize($this, $_)})

        # コントロールの登録
        $this.Controls.AddRange(@($this.pic1, $this.pic2, $this.txt1, $this.btn1))
    }
    # Loadイベント
    Form1_Load($sender, $e)
    {
        $this.Size = "800,600"
    }
    # Resizeイベント
    Form1_Resize($sender, $e)
    {
        $margin_top = 60
        $margin_bottom = 5
        $marign_left = 5
        $margin_right = 5

        $x = $marign_left
        $y = $margin_top
        $w = [int]($this.ClientSize.Width / 2) - ($marign_left+$margin_right)
        $h = $this.ClientSize.Height - ($margin_top+$margin_bottom)

        Write-Host ("pic1 x:{0},y:{1}" -f $x, $y)
        Write-Host ("pic1 w:{0},h:{1}" -f $w, $h)
        Write-Host ("img1 w:{0},h:{1}" -f $this.pic1.Image.Size.Width, $this.pic1.Image.Size.Height)
        $this.pic1.Location = ("{0},{1}" -f $x, $y)
        $this.pic1.Size = ("{0},{1}" -f $w, $h)

        $x = [int]($this.ClientSize.Width / 2) + $marign_left
        $this.pic2.Location = ("{0},{1}" -f $x, $y)
        $this.pic2.Size = ("{0},{1}" -f $w, $h)

        $this.txt1.Location = ("{0},{1}" -f 10, 20)
        $this.txt1.Size = ("{0},{1}" -f 600, 30)

    }

}

function Main
{
    $form = [Form1]::new()

    $form.ShowDialog()
}
Main

使い方

画像を左枠にドロップ&ドロップすると画像が表示されます。

マウスのホイールを回すと前後の画像に移動します。

表示された画像上でOCRしたい箇所の左上辺りでマウスの左ボタンを押し、押したまま対角線上に移動し、右下に到達したらボタンを離します。

上手く認識すると文字がテキストボックスに表示されますので、あとはお好きなように。

 

コメント