最近ゆっくり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したい箇所の左上辺りでマウスの左ボタンを押し、押したまま対角線上に移動し、右下に到達したらボタンを離します。
上手く認識すると文字がテキストボックスに表示されますので、あとはお好きなように。
コメント