ImageMagickとXnConvertを併用してみる

[備忘録] 自炊PDFの後処理関係のメモ。

XnConvertのバージョンが1.51に上がり,従来のBrightness, Contrast以外にも,白黒のレベル指定が出来るようになった。これで,ImageMagickでの -level high, low, gamma と同じ(多分ね)パラメーターの指定が可能となった。速度的にはLanczosでの拡大処理を含めてXnConvertの方が速いので,こちらをメインにしようかと思う。

ただ,XnConvertでは,モノクロ2値への変換で threshold値を選ぶことが出来ない。また,ImageMagickの shave (上下左右を切り取る),border (逆に上下左右を塗る) などの処理が出来ないみたいだ。shaveに関しては,cropとrotateを組み合わせれば良いようにも思うが,よく分からない。一番の問題点は,thresholdの値(閾値)を選べないということなので,この部分はImageMagickを使う方が良いだろう。

ということで,試しに一冊処理してみた。hoge.pdfをpdfimagesを使って,hoge_000.jpgからhoge_124.jpgにバラす処理は済んでいるものとする。そこの階層に行って,次のようにする。

nconvert -extract 0 -ratio -rtype lanczos -resize 200% 200%  -levels 60 255 -gamma 0.7  -posterize 64 -posterize 32 -posterize 16 -posterize 8 -posterize 4 -out png  -o nc_% -overwrite  *.jpg

コマンドの意味は次の通り。-extract 0 で赤チャンネル抽出。-ratio 以下は,lanczosアルゴリズムによる200%拡大。-levels 60 255 と -gamma 0.7 は ImageMagickでの level 60,255,0.7 と同じ。今回の本は,スキャンの具合が良くなくて,全体的に文字が薄くてボケた感じなので,白飛びさせると文字が消えてしまいそうだったので,high=255とマックスのままにした。

-posterize は減色(ポスタリゼーション)処理。64色から順に半分ずつにして,4色まで減色している。-out png は出力ファイル形式をPNGにするということ。-o nc_% は出力ファイルの名前の設定。これだと,元ファイルがhoge002.jpgの場合,nc_hoge002.png となる。-overwrite は出力ファイルと同じ名前があった場合に上書きするということ。nconvert による処理は以上で終了。

次は,ImageMagickを使う。mogrifyコマンドを使って,上下左右を削除して白で元にもどす。それから,閾値を80%にして,モノクロ2値化する。出力ファイルはtiffにして,FAX Group4で圧縮する。

mogrify -shave 40x40 -border 40x40 -bordercolor "#FFFFFF" -threshold 80% -format tiff -compress Group4 nc_*.png

続いて,tiffをpdfに変換。

mogrify -format pdf nc_*.tiff

最後に,pdfファイルをまとめて,hoge-mono.pdf にする。

pdftk nc_*.pdf cat output hoge-mono.pdf

こんな手順で,モノクロ2値化が完了。一冊毎にパラメーター変えるのなら,この程度の入力を毎回行なっても,それほど面倒ではない。パラメーター固定で複数の書籍を変換するのなら,やはりバッチ処理用のスクリプトを書いた方が良いだろうなあ,やはり。

スキャンした書籍の後処理など

自炊した(業者さんに依頼しているから厳密には違うかもだが)書籍のPDFの後処理についてのメモ。

数学関係の本は,ほとんどが白黒なので,最終的にはモノクロ2値にすることにした。PDFをページ毎の画像にばらして,処理したあと,最終的にFAX Group4圧縮でモノクロ2値のTiffにする。こうすると,1頁あたり50KBないしは100KB程度に収まる。

PDFをベージ毎の画像にばらすには,Acrobatあるいは,Xpdf付属のpdfimagesを使う。ただし,pdfimagesはjpeg指定なのにppmを吐き出すことがあって,信頼性にやや欠ける。でも,シェルから呼び出せるので,バッチ処理には便利。

画像の変換には,最初GIMPを使っていた。GIMPをシェルから使うために,Script-Fuというスキーム(Lispの方言)によるスクリプトをいくつか書いた。それはそれで良かったのだが,スピードがいまいち遅いので,ImageMagickに鞍替えした。ImageMagickはびっくりするくらい多機能(たとえば,傾き補正を自動でやってくれる)で,自炊本の後処理くらいなら,これで十分かなと思う。

具体的にはImageMagickのconvertというコマンドを用いて,赤チャンネル抽出(-channel Red -separate), 2倍拡大(-filter Lanczos -resize 200%), レベル補正(たとえば -level 20%,90%,0.6 とか), 傾き補正(-deskew 40%), モノクロ2値(たとえば -threshold 70% とか), 圧縮(-compress Group 4), TIFFで保存(出力先を hoge.tif などと), といった処理を行う。

これをすべてのページに施すのだが,Perlを使って一括処理することを試みた。実のところ,Perlの勉強も兼ねているという泥縄。まあしかし,入門書は本棚にあるし,ネットで適当に調べながら,Getopt::Long という便利なモジュールを利用して,なんとか動作するものが書けた。

しばらく実験していると,プログラムの設計に柔軟性がないことに気付く。もう少し汎用的なツールになっていると良いのにと思う。あるいは,Cコンパイラーのmakeみたいな構造にしても良いなあ,とも思う。このあたりは,思案中。

ともあれ,現時点では次のような感じで,一括変換を行なっている。

% pdf2mono hoge.pdf -level 40 240 0.7 -threshold 180

Perlでエクセル・データを処理する

とある件で、マイクロソフト・エクセル(MS Excel)のデータを取り扱うことになった。というか、入力済みのデータがエクセルのフォーマットという次第。ホントはテキスト・データでくれれば嬉しいのだが、どうも、事務関係はどこもかしこもエクセルばかりのようだ。

幸いなことに、Windowsを起動しなくても、Ubuntu上のOpen Office SpreadSheetで開くことができて、さらに、これを CSV (Comma Separated Values) 形式のテキストファイルとして保存できる。こうなれば、あとは楽である。やっぱりテキストファイルが一番だよねえ。

CSV形式のオプションとして、セパレーターが選べる。コンマで区切るよりもタブで区切る方が、ある意味合理的なので、タブで区切って保存する。これって、Tab Separated Values だから、TSVと呼ぶべきだと思うが、これでもやっぱりCSVと称するのだろうか。

それはともかく、あとは1行ずつ読み込んで処理すればよい。UNIXの標準入力から読み、標準出力に書き出すことにした。実際には、リダイレクトすれば良い。こうすると、プログラムは極めて簡単。入出力を書かなくてよいから。

while (<STDIN>) {
  chomp; # 行末コードを削除
  @data = split /\t/, $_; # $_は1行分のデータ
  # 以下、この配列に対する作業を行う。
}

こんな感じ。UNIXの便利さ、Perlの柔軟さが実感できる。

ファイル全体を一つのスカラー変数に代入するには

PerlでTeXの文書をちょちょっと変換するプログラムを作ろうとしたのだが、思わぬところで頓挫。Perlで処理する普通の作業ってのは、行単位で読み込んで処理して出力して、とかいうのが多い。よくあるサンプルを真似して作っていたのだが、大域変数$_ に入っているのがファイル全体だと思い込んでいたら、実は一行分だったのが迷走の原因だった。

ファイル全体、つまりすべての行を一つの文字列変数に代入するには、読み込む前に区切り記号を表す大域変数 $/ を未定義の状態、つまり undef にする。たったこれだけの為に、難渋してしまった。ということで、備忘録としてメモ。

{
  local $/ = undef;
  $_ = readline INFILE;
}

とすれば、ファイルハンドラー INFILE から読み込んだすべてのデータが $_ に入る。

文字コードを判別してTeXにかけるPerlスクリプト

とある事情で、Ubuntu上ではUTF-8およびShift_JISの2種類のTeX文書を処理している。一々 platex -kanji=utf8 とか platex -kanji=sjis とか、したくないので、文字コードを判別してから組版し、ついでにPDFに変換して文書ビューアで表示させる Perl スクリプトを作ってみた。

ユーザーは自分だけなので、思いっきり手抜きだが、まずまず役立っている。

#! /usr/bin/perl -w
#
# [usage] myplatex hoge
#  hoge.tex の文字コードを判別して、platex -kanji=KANJICODE とコンパイル
#  引き続いて dvipdfmx で PDF を作り、文書ビューアー evince を起動する
#  とりあえず,普段使っている UTF-8 と Shift_JIS のみサポート

use strict;

my $basename;   # hoge
my $texfile;    # hoge.tex
my $dvifile;    # hoge.dvi
my $pdffile;    # hoge.pdf
my $kanjicode;  # UTF-8, Shift_JIS

$basename = $ARGV[0];
$texfile = $basename."\.tex";
$dvifile = $basename."\.dvi";
$pdffile = $basename."\.pdf";

open (INFILE, "<$texfile") || die "File Not Found\n";
close INFILE;

$kanjicode = `nkf -g $texfile`;
chomp($kanjicode);

if ($kanjicode eq "UTF-8") {
 system("platex -kanji=utf8 -interaction=nonstopmode $texfile");
} elsif ($kanjicode eq "Shift_JIS") {
 system("platex -kanji=sjis -interaction=nonstopmode $texfile");
} else {
 die "Not supported\n";
}

system("dvipdfmx $dvifile");
system("evince $pdffile");

Perlでガウス記号

やってみると、Perlの数学関係はまったく充実していないことが判明 😳 。やっぱり PARI/GP のように数学に特化したものにする方が良いかも。そうは言っても、所詮は四則演算だけで済むので、必要な関数を自前で作ることにした。

まず整除ができない。13を5で割ると商が2で余りが3という計算。余りならば % という剰余演算子があり、13%5=3 となるらしいが、負の数に対しては Perl の実装によって結果が変わる可能性があると書いてあった。何と言語仕様で決めてないのかあ。まあ、文字列がメインターゲットってことですよね、やっぱり。

整数部分を与える関数 int(x) というのはあるが、xがマイナスのときはガウス記号とは違って int(-3.2)=-3 のようになるらしい。ああ、これも使い物にならない。

そこで、整数部分(ガウス記号)を与える関数を準備。ついでなので、切り捨て(ガウス記号)と切り上げの両方を作ってみよう。日本ではあまり普及してない呼び名だが、floor (フロアー、床)、ceiling (シーリング、天井)と言われるもの。動作は次のようになる。

 floor(3.14)=3、floor(3)=3、floor(-3.14)=-4

 ceiling(3.14)=4、ceiling(3)=3、ceiling(-3.14)=-3

効率は考えずに、とりあえず動くことだけを考えて作成。

# floor function
# &floor(3.14)=3, &floor(3)=3, &floor(-3.14)=-4
sub floor {
  my $x=$_[0];
  my $n=int($x);
  if ( $x==$n ) {
     $n;
  } elsif ( $x>0 ) {
     $n;
  } else {
     $x= -$x;
     $n=int($x);
     $n+=1;
     -$n;
  }
}

# ceiling function
# &ceiling(3.14)=4, &ceiling(3)=3, &ceiling(-3.14)=-3
sub ceiling {
  my $x=$_[0];
  my $n=int($x);
  if ( $x==$n ) {
    $n;
  } elsif ( $x > 0 ) {
    $n+=1;
    $n;
  } else {
    $x = -$x;
    $n = int($x);
    -$n;
  }
}

Perlで構造体

Perlの言語仕様にはやはり構造体は含まれていないようだ。しかし、追加モジュールというのがいろいろとあり、そこで実現されている。構造体を定義したければ、Class::Struct というモジュールを取り込む必要があるらしい。次のページはCに馴染みがある者に、てっとり早くPerlの要点を教えてくれて便利。

C言語を知る人へのPerl の基礎知識

C言語の

struct quadratic_form {
  int  left;
  int  center;
  int  right;
};
quadratic_form  some_qf;
some_qf.center = -3;

に対応させるには、次のように書けば良いのかな?

use Class::Struct

struct quadratic_form => {
    left   => '$',
    center => '$',
    right  => '$'
};
 
my $some_qf = new quadratic_form();
$some_qf->center(-3);

初めてのPerl

ランダル・L. シュワルツ,トム フェニックス
Amazonランキング:34323位
Amazonおすすめ度:


2次形式の変換作業を計算機にさせようと :mrgreen: Perlの入門書を本棚から引っ張り出してきた。良く知っている PASCAL とか C で書いてもいいのだが、まあ、所詮は整数の四則演算だから言語はどれでも大差ないから、復習も兼ねて。というか、以前、TeXの数式部分を画像に変換するプログラムを書いてから大分経つので、すっかり忘れてしまって、全くの初心者に戻っているのだけど(苦笑)。

Perl特有の習慣に未だ馴染めない所もあるが、まずまず理解しやすい本。実際に動作するサンプル・プログラムが付いているともっと良いと思うが。差し当たって、2次形式のデータ型を定義するにはどうするかとあちこち見るのだが、構造体ってのはないんですかね。


パスカルのレコード型、Cの構造体に対応するものが見つからないが、ハッシュで表現しろということなのだろうか。2次形式
\[ f(x,y)=ax^2+bxy+cy^2 \]
を (a,b,c) と略記して、これを一つのデータ型にしたいのであるが・・・。

パスカルなら、例えば、

type 
  quadratic_form = record
    left : integer;
    middle : integer;
    right : integer;
  end;

C は、えっと・・・うろ覚えだが、

struct quadratic_form {
  int  left;
  int  middle;
  int  right
};

みたいに定義すれば、quadratic_form 型というのが定義できる。

これをハッシュでやるとどうなるのかな? 個別の2次形式、例えば some_quadratic_form=(2,3,4) を表すには

%some_quadratic_form = (
  "left" => 2, 
  "middle" => 3,
  "right" => 4
);

などとするのだろうが、こういうものを包括的に一つの型として登録することはできないのだろうか。それとも、そういうことはしないという風土なのか。なんでもそうだけど、初心者はこんな簡単そうなところで悩むからつらいよねえ。