2

scansnapで自炊した本をkindleで読めるように補正する(2)

橋本商会 scansnapで自炊した本をkindleで読めるように補正するをOpenCV使ってC++で書き直したら60倍速くなった。635ページのオライリーの本が約1分半で処理できたし、画質も前のJRuby版より少し読みやすくなった気がする。


こういう事をする。

■やっている事
scansnapで自炊した本をそのままkindleで表示するには色々問題がある。

  • kindleの解像度は800×600だけど、画面内にページ位置等のUIが入るし画面を縦横回転させたり拡大縮小できるので、表示時にkindle上でリサイズされる。この時、線が細くて薄い部分が消滅してしまって、文字がかすれて読めなくなってしまう。
  • scansnapで取り込んだままのPDFファイルでは、コントラストが弱くてとても読みづらい。
  • ページの上下左右の余白が邪魔で、kindleに全画面表示した時に小さくなってしまう

kindle上でリサイズされる事を前提として、文字が消えてしまうような細い部分を太くしてやるしかない。
しょうがないので、コントラストを上げたり、拡大縮小されるのを前提として先にアンチエイリアス?的な処理をしたり、余白を切り取ったり、リサイズしたりするようにした。
上下左右を裁ち落とし→リサイズ→グレースケール化→2値化→黒の周りを#999999で太らせるという処理をしている。


上下左右裁ち落とし→グレースケール化→2値化→黒の周りを#000000で太らせる→リサイズ
という処理に変えた。JRuby+javax.imageioでピクセル単位に処理すると、解像度が大きい画像を処理するのが苦しかったので先にリサイズするようにしていた。
OpenCVは十分速いので、高解像度のまま処理して最後に縮小するように変更した。これでサイズが大きい本でも綺麗に変換できる。


■使う
OpenCV1.00以上と、boostのboost::system, boost::filesystem, boost::program_optionsのインストールが必要。少なくともMacとUbuntuでは動く。
その辺はgithub/shokai/scansnap_adjust_images_kindleに書いた。


gitリポジトリを持ってきてコンパイルする

git clone git://github.com/shokai/scansnap_adjust_images_kindle.git
cd scansnap_adjust_images_kindle/kindlize_images

# for Mac
make -f Makefile.macosx

./configureの作り方がよくわからないのでとりあえずMac用のMakefileだけ置いてある。
Linuxとかでも、opencv、boost::filesystem、boost::progoram_optionsをインストールしてあればコンパイルはできる。(この組み合わせは色々なOSでよく使ってる)


オライリーの判型の本をkindle用に補正する。上下左右の余白を削除して白黒になって文字が太くなる。イラストは見づらくなるかもしれない。
mkdir ~/tmp/mybook_kindle
./kindlize_images --help
./kindlize_images -i ~/tmp/mybook/ -o ~/tmp/mybook_kindle/ -t 190 -w 1200 -h 1600 --cleft 120 --cright 120 --ctop 150 --cbottom 150


はじめにPDFから連番のJPEG画像に書き出して、それからこのツールを使って変換して、最後に連番画像をPDFにまとめるという使い方を想定している。
PDFから連番画像を書き出す方法は、前の記事やgithubのREADME.mdに書いた。
連番画像を1つのPDFへ結合するのは、gitリポジトリの中に一緒にimages2pdf.appというautomatorアプリを入れて置いたのでそれを使うと良い。


■ソースコード
100行切った。boost::filesystemのおかげでディレクトリ内一括処理が書きやすかった。
JRuby使って書くよりもコードが35行短くなったけど、最初に試行錯誤するにはJRuby+javax.imageioでやる方が手軽だったな。

kindlize_images/kindlize_images.cpp at master from shokai's scansnap_adjust_images_kindle – GitHub
#include "cv.h"
#include "highgui.h"
#include <boost/program_options.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <iostream>
using namespace boost;
using namespace std;
namespace fs = boost::filesystem;

IplImage *adjust_image(IplImage *img, program_options::variables_map argmap){
int cleft = argmap["cleft"].as<int>();
int cright = argmap["cright"].as<int>();
int ctop = argmap["ctop"].as<int>();
int cbottom = argmap["cbottom"].as<int>();

const int w = img->width - cleft - cright;
const int h = img->height - ctop - cbottom;

cvSetImageROI(img, cvRect(cleft, ctop, w, h));

IplImage *img_gray = cvCreateImage(cvSize(w,h), IPL_DEPTH_8U, 1);
cvCvtColor(img, img_gray, CV_BGR2GRAY);

// 2値化
IplImage *img_bin = cvCreateImage(cvSize(w,h), IPL_DEPTH_8U, 1);
cvThreshold(img_gray, img_bin, argmap["threshold"].as<int>(), 255, CV_THRESH_BINARY);

// 文字を太らせる
cvErode(img_bin, img_bin, NULL, 1);

// リサイズ
int width = argmap["width"].as<int>();
int height = argmap["height"].as<int>();
IplImage *img_resized;
double scale = ((double)h)/w;
if(((double)height)/width < scale){ // 縦長
img_resized = cvCreateImage(cvSize((int)(height/scale), height), IPL_DEPTH_8U, 1);
}
else{ // 横長
img_resized = cvCreateImage(cvSize(width, (int)(scale*width)), IPL_DEPTH_8U, 1);
}
cvResize(img_bin, img_resized, CV_INTER_LINEAR);

cvReleaseImage(&img_bin);
cvReleaseImage(&img_gray);
return img_resized;
}

int main(int argc, char* argv[]) {
program_options::options_description opts("options");
opts.add_options()
("help", "ヘルプを表示")
("width,w", program_options::value<int>(), "output width")
("height,h", program_options::value<int>(), "output height")
("threshold,t", program_options::value<int>(), "binarize threshold")
("input,i", program_options::value<string>(), "input directory name")
("output,o", program_options::value<string>(), "output directory name")
("cleft", program_options::value<int>(), "crop left (pixel)")
("cright", program_options::value<int>(), "crop right (pixel)")
("ctop", program_options::value<int>(), "crop top (pixel)")
("cbottom", program_options::value<int>(), "crop bottom (pixel)");
program_options::variables_map argmap;
program_options::store(parse_command_line(argc, argv, opts), argmap);
program_options::notify(argmap);
if (argmap.count("help") || !argmap.count("input") || !argmap.count("output") ||
!argmap.count("threshold")) {
cerr << "[input, output] required" << endl;
cerr << opts << endl;
return 1;
}

string in_dir = argmap["input"].as<string>();
fs::path path = complete(fs::path(in_dir, fs::native));
fs::directory_iterator end;
for (fs::directory_iterator i(path); i!=end; i++){
string img_fullname = in_dir + i->leaf();
cout << img_fullname << endl;
IplImage *img, *img_result;
img = cvLoadImage(img_fullname.c_str());
if(!img){
cerr << "image file load error" << endl;
}
else{
img_result = adjust_image(img, argmap);
string out_filename = argmap["output"].as<string>() + "/" + i->leaf();
cvSaveImage(out_filename.c_str(), img_result);
cvReleaseImage(&img);
cvReleaseImage(&img_result);
}
}
}

28

scansnapと裁断機を買って本を電子化しまくる

scansnapと裁断機を買った。合計3万6000円ぐらいだったと思う


最近はちょっとした待ち時間に、iPhoneでpdf化したRubyレシピブックをよく読んでいる。
写真 2

漫画なんかも、24インチの液晶に出せばむしろ元の本より大きくなっていい。
あと、中高の頃に毎年度みんなの作文や部活の大会記録がまとめられた200ページぐらいの本が作られていたんだけどそれもスキャンして全部パソコンに入れておいた。OCRもかけてあるので、これで黒歴史を検索できる。どこでもこういう本を見れるのは、友達に会った時とかにネタになって面白い。


買ったのは11月に出たscansnap s1300。サイズはティッシュ箱ぐらい。3,4秒で1枚、裏表まとめて読み込める。普通のフラットヘッドスキャナと違ってプリンタの様に自動でページ送りしてくれるので、500ページの本を20分ちょっとで読み込めた。名刺だとサイズが小さいのでもっと速い。
一冊の本を解体するのに1分、スキャンしてpdf出力に20分(この間、原稿を100枚ずつぐらい入れる)、必要であればスキャン後にOCRをかけるのに10分ぐらいかかる。
webで検索すると一度に20枚程度しかセットできないと書いてあるが、そうでもない。確かに厚さを測るセンサー?か何かが働いていて最初から100枚入れておくと「原稿が読み取れない」というエラーが出るが、最初に20枚ぐらい入れてスキャン開始して、すぐ80枚ぐらい突っ込んでスキャンさせている。

ネットワーク共有すると普通のスキャナとしてしか使えないので、Windows XP homeが入っている古いネットブックにscansnapをつないで、Tight VNC Serverを起動させておいてMac OSXに最初から入っている画面共有で遠隔操作している。


FUJITSU ScanSnap S1300 FI-S1300
富士通 (2009-11-21)
売り上げランキング: 720
おすすめ度の平均: 5.0
5 ストレスフリー


本を解体する為にこの裁断機を買った。14000円ぐらい。
大型ペーパーカッター 裁断機

大型ペーパーカッター 裁断機



まだ必要ないので買ってないけど、替え刃も発見した。
コピー用紙500枚らくらくカット/人気 商品大型ペーパーカッター用『替え刃』

コピー用紙500枚らくらくカット/人気 商品大型ペーパーカッター用『替え刃』


ジャンプぐらいの厚い雑誌でも切れる。このサイズの裁断機では妙に安い(他の半額程度)だが、そのかわり届いた時点で油でぬるぬるしている。
軍手とぞうきんを用意しておいて、よく拭いた。

刃の部分には茶色い油?が付着しているので、試しにいらない本を裁断してみて油の拭き残しが無いか確かめる必要がある。

油がついた。(古本ぐらしは新しいのを持っているので、2003年版を試し切りに使った)
R0013859.JPG


■裁断する
まずスキャンできるように本をバラバラにする。

表紙を外す
R0013860.JPG


しっかり押さえる
R0013861.JPG


切る
R0013862.JPG


切る。裁断機の刃が超鋭いので、あんまり力はいらない。
R0013863.JPG


バラバラにした本をscansnapに入れる
R0013867.JPG


iPhoneアプリのGoodReaderを使って転送して、読める。
写真 1



■OCRをかける
scansnap managerにOCR機能が内蔵されていて、pdfを検索可能な状態にできる。
Windows版はまともだが、Mac版は日本語と英語どちらかしか選べない。日本語でOCRをかけるとローマ字が全角になってしまう。

縦書きの認識はうまくいっていない。自動認識らしいが、横書きだと誤認識してしまう。手動で指定する事も出来ない。


技術書とかは、本の状態よりも検索できた方が便利な事が多い気がする。先に本の内容と流れを把握していれば。
scanしたRubyレシピブック



■スキャンの設定
画質は「スーパーファイン」にしている。ちょっとファイルサイズ大きいけど、ストレージはどんどん大きくなるから別に構わない。

いろいろ試したが、「カラーモードの選択」を「自動」にしていると、例えばこういうページを「グレースケール」ではなく「白黒」として誤認識してしまう事があった。式の部分の背景がグレーの部分がおかしくなってしまう事がある。
写真 1

なので毎回手動でカラーモードは指定している。

「原稿の向きを自動的に補正」も、たまに間違えるのでオフにしている。上側を先にscansnapに入れれば必要ない。
「白紙ページを自動的に削除」はきっちり働くのでオン。
「継続読み取りを有効」はしておいたほうがいい。継続して1つのpdfファイルに追記していける。


■PDFの編集
Windows用のpdf編集ソフトがついている。ページの回転や結合ができて、scansnapの仕上げにはUIがAcrobat Proよりも優れている。
MacではAdobe Acrobat Professional等が必要。


■電子化したくない本
文系の本は、俺はライティングスペースに鉛筆で書き込むので電子化したくない。
あと、やっぱりパラパラ流し見できないので、本の内容と流れが頭に入るまでは電子化したくないな。

文庫で縦書きの小説は電子化してもいいが、ビューアとしてiPhoneの画面サイズだと絶妙に読みにくい。縦書きだと画面からはみだすか、超小さい字になる。


やっぱり技術書と漫画と名刺やはがき、説明書、場所を取るから捨てたいんだけど数年後に確実に見たくなる本(卒業文集とか?)をスキャンすればいいんじゃないだろうか。
あと数年すれば電子化した本を再び製本しなおす方法も整ってくると思うし。