2013

Twitter Digest 2013-09-16

  • 台風だけど,まあやるんだろうなあ,と確認。おや,まさか。お休みですか? 06:07:33, 2013-09-16
  • モバイルサイトは繋がるのだけど,メインのPCサイトが激重で繋がらない。そちらでも確認したいのだけど。 06:43:29, 2013-09-16
  • 悲しいお知らせなう。お休みは午前中だけということでした。やれやれ。 14:41:48, 2013-09-16

Twitter Digest 2013-09-15

  • あれ?雨やんだと思ったら,晴れてきた。まあ,一時的なんだろうけど。明日の朝が心配。というか,休みにならないかなあ(^^;; 14:13:18, 2013-09-15
  • 外心と重心が一致する三角形が正三角形であることのベクトルによる証明を思いついた。 14:44:17, 2013-09-15
  • 三角形ABCの外心Oが重心でもあれば,ベクトルOA+OB+OC=0となる。よって,OA=PQ, OB=QR, OC=RPとなる三角形PQRが作れて,PQRは正三角形になる。これから,OA, OB, OCのなす角は120度である。終わり。 14:46:12, 2013-09-15

LaTeX エラー:Runaway argument? File ended while scanning use of \@writefile.

昨日の朝,LaTeX Beamer でプロジェクター用の資料を作っていたときのこと。奇っ怪なエラーに遭遇した。

Runaway argument?
{\headcommand {\beamer@framepages {62} 
! File ended while scanning use of \@writefile.
 
                \par 
l.130 \begin{document}

未定義マクロのためエラーが出て,修正して再コンパイルしたところ,上記のエラーメッセージ。文法的にはエラーはもうないはずなのに。それに,\@writefile とは一体?

発表を数時間後にひかえていたので,焦りまくった。数分後,ふと気がついて補助ファイルをすべて削除したら,エラーはなくなった。つまり,hoge.tex をコンパイルすると,hoge.aux とかが生成されるのだが,これが変になっているために,再コンパイルのときにエラーが出るという仕組みであった。

しかし,エラーのたびにターミナルあるいは,ファインダーから削除するのは面倒である。エディターのTeXShopに,補助ファイルを削除する,というメニューがあれば良いのになあ・・・と思ってあちこち見たら,あった。メニューの編集のところに「作業ファイルを削除する」という項目があったのだ。

iPhoneアプリ、CamScanner

先日(たしか火曜日),職場で新聞読んでいたら「立川のウドラ」なる記事があり,面白かったのでメモ代わりに(iPhoneで)写真をとった。ふと,そう言えば,いくつかScanソフトをダウンロードしてたなあと想い出し,CamScanner を起動して読み込んでみた。もしかしたら,使うの初めてかも。

新聞記事の写真を選ぶと,縦横の線が入っているためか,自動的に記事の部分だけを認識していて,チェックボタンを押すと,選択部分をスキャンする。白黒化なども簡単に出来て,これはなかなかに便利だと感心した。

これが記事の写真。

カメラロール-4277

これがスキャンしたもの。

立川のウドラの新聞記事(2013年9月3日) CamScanner版

OpenCVで自炊本をモノクロ2値に変換してみた

自炊代行業者さん(Bookscan)に依頼した本のPDFがたくさんあるのだが,読みやすく美白化して白黒2値に変換したい。それを実現するためのプログラム。まだ試作段階だが,とりあえず動作している。以前,GIMP用のスクリプトやImageMagick用のPerlスクリプトで行っていたことを,OpenCVでやってみた。以前のバージョンに比べて,明らかにスピードが速い。OpenCVはもちろんC++でプログラム書いたことないので,無事にコンパイル出来たときは,ちょっと嬉しかったな。

参考にしたのは,次のページ。参考というよりは,ほとんどそのまま頂いている。有難きは先達なり。

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

このページに書かれているプログラムをひな形として,自分の目的に合うように処理内容をあちこち変えた。実は,boost::filesystemのバージョンが上記ページが書かれた頃から変わっていたため,そのままではコンパイル出来なかった。また,OpenCVのバージョンも上がっていて,ライブラリーの名前などが以前とは変わっているようなので,それに応じてMakefileも少し修正を必要とした。ちなみに,OpenCVもboostもHomebrewでインストールした。バージョンは brew info によれば,それぞれ opencv: stable 2.4.5, boost: stable 1.54.0 (bottled) となっている。

やっていることは,次の通り。

  • 赤チャンネルだけを抽出することでグレースケールに変換
  • 上下左右の余白を白で塗りつぶす
  • Lanczos法により2倍に拡大
  • ルックアップテーブルを作成することにより,レベル補正,ガンマ補正
  • 2値化
  • PNGで保存

レベル補正,ガンマ補正の式が多分正しくない。gamma=1のときは直線補間だから正しいと思うが,それ以外のときの式がわからない。ImageMagickの level high low gamma と同じにしたいのだが。これは今後の課題。

PNGで保存しているが,可能であれば,CCITT G4圧縮のTIFFで保存したい。これも今後の課題。

#include "cv.h"
#include "highgui.h"
#include <boost /program_options.hpp>
#include </boost><boost /filesystem/operations.hpp>
#include </boost><boost /filesystem/path.hpp>
#include </boost><boost /filesystem/fstream.hpp>
#include <iostream>
using namespace boost;
using namespace std;
namespace fs = boost::filesystem;

//---------------------------------------------------------------
// my_Level
// レベル補正・ガンマ補正 
// (ImageMagickの convert -level white_point black_point gamma と同じにしたいのだが)
// src    = 入力画像
// dst    = 出力画像
// high   = ホワイトポイント (0..255)
// low    = ブラックポイント (0..255)
// gamma  = ガンマ補正値
// 注意:暫定版。ガンマ補正の式はこれで正しいのか不明。
// 0<x &lt;1 と正規化したとき,f(x)={(x-low)/(high-low)}^{1/gamma} としてみた。
//---------------------------------------------------------------

// グローバル変数
uchar g_LUT&#91;256&#93;; // Lookup table用の配列
CvMat g_lut_mat;  // それをopencvの行列とみなしたもの

void make_lut(int high, int low, double gamma){
    int i;
    double x;
    const double p = 1.0 / gamma ;
    const double a = (double) low;
    const double b = (double) high;
  
    //補正用のルックアップテーブルの作成
    for (i=0; i<low; i++) { g_LUT&#91;i&#93; = 0; }
    for (i=high; i&lt;256; i++) { g_LUT&#91;i&#93; = 255; }
    for (i=low; i<high; i++) 
    {
      x = (double) i;
      // 16階調にする場合 16x16=256
      //g_LUT&#91;i&#93; = cvCeil( ((x-a)/(b-a)) * 15 ) * 16;
      g_LUT&#91;i&#93; = cvCeil( 255.0 * pow((x-a)/(b-a), p) );
    }
 
    //CvMatへ変換
    g_lut_mat = cvMat(1, 256, CV_8UC1, g_LUT);
}

void my_Level(IplImage* src, IplImage* dst) {
  //ルックアップテーブル変換
  cvLUT(src, dst, &g_lut_mat);
}

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

  const int w = img->width;
  const int h = img->height;
  const int w1 = 2*w;
  const int h1 = 2*h;
  const int x1 = cleft;
  const int x2 = w-cright;
  const int y1 = ctop;
  const int y2 = h-cbottom;

  IplImage *img_gray;

  //赤チャンネルを抽出してグレースケールの画像を得る
  //チャンネル毎に分割して,赤チャンネル以外は捨てる
  // IplImageはB,G,Rの順に格納されている
  // アルファチャンネルは用いないので,5番目の引数はNULLにしておく
  // cvSplit(img,channelB,channelG,channelR,NULL);
  img_gray = cvCreateImage(cvSize(w,h),IPL_DEPTH_8U,1);
  //cvCvtColor(img, img_gray, CV_BGR2GRAY);
  cvSplit(img,NULL,NULL,img_gray,NULL);

  //周囲を白で塗る
  cvRectangle(img_gray, cvPoint (0,0), cvPoint (x1,h), cvScalar (255), CV_FILLED, 8, 0);
  cvRectangle(img_gray, cvPoint (x2,0), cvPoint (w,h), cvScalar (255), CV_FILLED, 8, 0);
  cvRectangle(img_gray, cvPoint (0,0), cvPoint (w,y1), cvScalar (255), CV_FILLED, 8, 0);
  cvRectangle(img_gray, cvPoint (0,y2), cvPoint (w,h), cvScalar (255), CV_FILLED, 8, 0);

  //2倍に拡大
  IplImage *img_resize = cvCreateImage(cvSize(w1,h1), IPL_DEPTH_8U, 1);
  cvResize(img_gray, img_resize, CV_INTER_LANCZOS4);

  //レベル補正
  IplImage *img_level = cvCreateImage(cvSize(w1,h1), IPL_DEPTH_8U, 1);
  my_Level(img_resize, img_level);

  if (threshold > 0) {
    // 2値化する場合
    IplImage *img_bin = cvCreateImage(cvSize(w1,h1), IPL_DEPTH_8U, 1);
    // ガウシアンフィルタで平滑化を行う
    // cvSmooth (img_level, img_level, CV_GAUSSIAN, 5);
    // 2値化
    cvThreshold(img_level, img_bin, threshold, 255, CV_THRESH_BINARY);
    //cvThreshold(img_level, img_bin, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); // 大津の手法
    // メモリー開放
    cvReleaseImage(&img_gray);
    cvReleaseImage(&img_resize);
    cvReleaseImage(&img_level);
    // 2値化した画像へのポインターを返す
    return img_bin;
  } else {
    // グレースケールで出力する場合
    // メモリー開放
    cvReleaseImage(&img_gray);
    cvReleaseImage(&img_resize);
    // レベル補正したグレースケール画像へのポインターを返す
    return img_level;
  }
}

int main(int argc, char* argv[]) {
  program_options::options_description opts("options");
  opts.add_options()
    ("help", "ヘルプを表示")
    ("high", program_options::value</int><int>()->default_value(255), "level high")
    ("low", program_options::value</int><int>()->default_value(0), "level low")
    ("gamma", program_options::value<double>()->default_value(1.0), "level gamma")
    ("threshold,t", program_options::value<int>()->default_value(-1), "binarize threshold")
    ("input,i", program_options::value<string>(), "input directory name")
    ("output,o", program_options::value</string><string>(), "output directory name")
    ("cleft", program_options::value<int>()->default_value(0), "crop left (pixel)")
    ("cright", program_options::value</int><int>()->default_value(0), "crop right (pixel)")
    ("ctop", program_options::value</int><int>()->default_value(0), "crop top (pixel)")
    ("cbottom", program_options::value</int><int>()->default_value(0), "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("high") || !argmap.count("low")) {
    cerr < < "&#91;input, output, high, low&#93; required" << endl;
    cerr << opts << endl;
    return 1;
  }

  int high = argmap&#91;"high"&#93;.as<int>();
  int low  = argmap["low"].as</int><int>();
  double gamma  = argmap["gamma"].as<double>();

  // ルックアップテーブルを作成する。ルックアップテーブルはグローバル変数 g_lut_mat である。
  make_lut(high,low,gamma);

  string in_dir = argmap["input"].as<string>();
  fs::path path = complete(fs::path(in_dir));
  fs::directory_iterator end;
  for (fs::directory_iterator i(path); i!=end; i++){
    string img_fullname = in_dir + i->path().filename().string();
    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&#91;"output"&#93;.as<string>() + "/" + i->path().filename().string();
      string out_filename = argmap["output"].as</string><string>() + "/" + i->path().stem().string() + ".png";
      cvSaveImage(out_filename.c_str(), img_result);
      cvReleaseImage(&img);
      cvReleaseImage(&img_result);
    }    
  }
}

ガンマ補正の式がわからない

OpenCVを使って,自炊した本の画像の変換は,まずまず上手く行っているのだが,ガンマ補正のところだけが分からない。しかたないので,とりあえずは gamma=1 つまり直線補間しているのだが,どこが間違っているのだろうか。

//—————————————————————
// cv_Level
// レベル補正・ガンマ補正
// (ImageMagickの convert -level white_point black_point gamma と同じにしたいのだが)
// src = 入力画像
// dst = 出力画像
// high = ホワイトポイント (0..255)
// low = ブラックポイント (0..255)
// gamma = ガンマ補正値
// 注意:暫定版。ガンマ補正の式はこれで正しいのか不明。
// 0high)
{
LUT[i] = 255;
}
else
{
x = (double) i /255.0;
LUT[i] = cvCeil( ((pow(x,p)-a)/(b-a)) * 255.0 );
//LUT[i] = (int) ( pow( ( ((double)i – (double)low) / ((double)high – (double)low) ), 1.0 / gamma ) * 255.0 );
//LUT[i] = (int)(pow((double)i / 255.0, 1.0 / gamma) * 255.0);
}
}
*/
for (i=0; i

HomebrewのImageMagickをupgradeしたのだが

久しぶりに ImageMagick をターミナルから使おうとしたら,

dyld: Library not loaded: /usr/local/lib/libtiff.3.dylib

というメッセージが出て,起動しなかった。いろいろ入れているうちに,シンボリック・リンクが壊れているのかも。というか,そもそも ImageMagick をどれで入れたのか記憶がない。Homebrewだった気もするが,自信なし。ということで,brew upgrate imagimagick として最新のにした。そのときのコンソール出力が以下。

rdguru:~ aqua$ brew upgrade imagemagick
==> Upgrading 1 outdated package, with result:
imagemagick 6.8.6-3
==> Upgrading imagemagick
==> Installing imagemagick dependency: libtool
==> Downloading http://ftpmirror.gnu.org/libtool/libtool-2.4.2.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/libtool/2.4.2 --program-prefix=g --enable-ltdl-install
==> make install
==> Caveats
In order to prevent conflicts with Apple's own libtool we have prepended a "g"
so, you have instead: glibtool and glibtoolize.

This formula is keg-only: so it was not symlinked into /usr/local.

Xcode 4.2 and below provide glibtool.

Generally there are no consequences of this for you. If you build your
own software and it requires this formula, you'll need to add to your
build variables:

    LDFLAGS:  -L/usr/local/opt/libtool/lib
    CPPFLAGS: -I/usr/local/opt/libtool/include

==> Summary
 /usr/local/Cellar/libtool/2.4.2: 66 files, 2.2M, built in 22 seconds
==> Installing imagemagick
==> Downloading http://downloads.sf.net/project/machomebrew/mirror/ImageMagick-6.8.6-3.tar.bz2
######################################################################## 100.0%
==> ./configure --disable-osx-universal-binary --without-perl --prefix=/usr/local/Cellar/imagemagick/6.8.6-3 --enable-sh
==> make install
  /usr/local/Cellar/imagemagick/6.8.6-3: 1435 files, 21M, built in 2.2 minutes

よく分からないのだが,Apple標準のlibtoolと衝突してるから,インストールはしたけど,/usr/local にシンボリック・リンクを作ってないよ,ということだろうか。

新しきことを始めるのは難しいなり

自炊したPDFの後処理のために,opencvを勉強して自前のプログラムを作るか,それとも,せっかくWindowsマシンも買ったことだし,Ralpha などで処理するか,悩み中。自分の目的のためには,多機能な画像処理ソフトは実は不要で,やりたいことは,上下左右余白の削除,レベル補正,2倍にリサンプリングしてグレースケール化あるいは白黒2値化,くらいなのである。

で,opencvの出番。ImageMagickでも良いが,以前の実験だと,1ページあたり5秒くらいかかるので,200ページの本で1000秒,つまり20分くらいかかるのである。もう少し速くならないかなあと思うのである。

しかし,まったく新しいことを始めるのは気が重い。ワタシのプログラム経験と言えば,ずっっっっっっと昔,CP/M上でTurbo Pascalとか Mac Plus (68Kでメモリー1M)上でLightning Pascal とか Lightning C とか,そんな時代だからなあ。一応,統合環境のはしりではあると思うが,基本的には,プログラム本体を文書エディターで作成して,ターミナルから cc hoge.c とかいう簡単なことしか出来ない。

とか思うと気が重いのである。思い切って始めてしまえば何てことはないのかもしれないが,近くに先達がいないのもあって,どうしたものかと思いあぐねるのである。

とか考えると,知り合いで未だにTeXをやらない人の気持ちも分かるというものである。ワタシなんぞは,もう20年以上使っているものだから,文章を書くと言えば,TeX以外考えられないが,初めての人には敷居が高いのだろうなあ,と思う。第一,自分でインストールとかしなきゃならないし。

ということで,とりあえず XCode は最新にしたし,Homebrew で opencv もインストールしてあるのだけれど,未だに “Hello World!” 的な,はじめの一歩を踏み出せない状態なのであります。誰かひな形作って,背中押してくれないだろうか・・・。

実家でのWiMAX

ひさびさの実家なので,ネット環境をチェックしてみた。

まずは,持参した au の WiMAX/3G モバイルルーター DATA08W による WiMAX の結果。

お!まずまずのスピードではないか。東京の自宅だとWiMAXは3Mbit/secくらいなので,ずっと速い。

続いて,実家の ADSL+無線LAN での結果。

うーむ。まあ,電話局から遠いから仕方ないですね。

しかし,こういう結果だと,ADSLを解約して WiMAXの据置型にした方が良いのではないかと思ってしまう。月々の料金も似たようなものだし。