ImageMagickとマルチスレッドは混ぜるなキケン!?
画像処理をする要件が出たので手軽に扱えるライブラリは無いものかと探した結果、ImageMagickに行き着きました。
ライブラリを利用して、油彩化とシャープネスを組み合わせた加工を施すプログラムです。
この処理をスレッドに割り当て、マルチスレッドで画像処理を行うロジックを実装してみました。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <ImageMagick/wand/MagickWand.h> #define SRC_BASE_PATH "src" /* #apt-get install libmagickwand-dev gcc -o img_process img_process.c `pkg-config Wand --cflags --libs` -lpthread ./img_process -N %thread_num */ #define QUALITY 90 #define STR_PATH_MAX 32 #define STR_NUM_MAX 5 char *progname; void usage(void) { fprintf(stderr, "%s: [-N num_threads]\n", progname); } void *thread_main(void *arg) { MagickWand *m_wand; char rpath[STR_PATH_MAX], wpath[STR_PATH_MAX]; int w, h, i; sprintf(rpath, "%s/nature%d.png", SRC_BASE_PATH, (long)arg); printf("[input] %s\n", rpath); /* debug */ sprintf(wpath, "nature%d.png", (long)arg); /* 加工後のファイルは実行ファイルと同じディレクトリに出力 */ printf("[output] %s\n", wpath); /* debug */ /* 初期化処理 */ MagickWandGenesis(); m_wand = NewMagickWand(); /* 画像ファイルの読み込み */ if(MagickReadImage(m_wand, rpath) == MagickFalse){ puts("ファイルを読みめませんでした"); exit(EXIT_FAILURE); } /* サイズ変換処理 -小さくすると高速化になる- */ #if 0 w = MagickGetImageWidth(m_wand); h = MagickGetImageHeight(m_wand); MagickResizeImage(m_wand, w / 2, h / 2, LanczosFilter, 1.0); /* */ #endif /* 油彩画加工+シャープネス */ MagickOilPaintImage(m_wand, 2); MagickSharpenImage(m_wand, 0, 50); MagickSetImageCompressionQuality(m_wand, QUALITY); /* 画像ファイル書き出し */ if(MagickWriteImage(m_wand, wpath) == MagickFalse){ exit(EXIT_FAILURE); } /* 後片付け */ if(m_wand){ m_wand = DestroyMagickWand(m_wand); } MagickWandTerminus(); printf("%s was wrote.\n", wpath); pthread_exit(NULL); } int main(int argc, char *argv[]) { MagickWand *m_wand; int w, h, thread_num = -1, target_cpu; long i; pthread_t *threads; progname = argv[0]; if(argc != 3){ /* 引数違反 */ usage(); exit(EXIT_FAILURE); } else{ thread_num = atoi(argv[2]); /* スレッド数を指定 */ } threads = calloc(thread_num, sizeof(pthread_t)); for(i = 0; i < thread_num; i++){ if((pthread_create(threads + i, NULL, thread_main, (void *)i)) == -1){ puts("pthread_create(3) error"); exit(EXIT_FAILURE); } } printf("thread_num:%d\n", thread_num); /* JOIN */ for(i = 0; i < thread_num; i++){ pthread_join(threads[i], NULL); } free(threads); return 0; }
「-N」に続く引数に対応する画像をカレントディレクトリにあるsrcディレクトリから読み込み、画像処理を施すプログラムです。「-N 2」という引数を与えるとsrc/nature0.pngとsrc/nature1.pngというファイルを読み込んでそれぞれマルチスレッドで処理します。
しかし、コンパイルし、実行すると以下のようなエラーが出て終了してしまいます。
thread_num:2
[input] src/nature3.png
[output] nature0.png
[input] src/nature4.png
[output] nature1.png
nature1.png was wrote.
imgproc: magick/semaphore.c:262: LockSemaphoreInfo: Assertion `semaphore_info != (SemaphoreInfo *) ((void *)0)' failed.
これをgoogleで検索した結果、よく報告される現象なようです。最新版なら問題ないだろうと思い、ソースからビルドすることを考えてみます。そこで、最新版のImageMagickをダウンロードします。
[本家JPミラー] ftp://ftp.kddlabs.co.jp/graphics/ImageMagick/
私はこの中のbeta版のImageMagick-6.7.1-1.tar.bz2をダウンロードしました。
ビルドする前に依存関係にあるパッケージが山ほどあるので、インストールしておきます。
環境:Linux debian 2.6.39-2-amd64 [debian wheezy]
#apt-get install libfreetype6-dev libfreetype.la #apt-get install xorg-dev Xos.h #apt-get install libbz2-dev bzlib.h #apt-get install libtiff4-dev tiffconf.h #apt-get install liblcms1-dev lcms.h (libcms2-devだとlcms2.hがインストールされる) #apt-get install liblqr-1-0-dev lqr.h #apt-get install libglib2.0-dev glib.h(lqr.hで内部依存) #apt-get install graphviz-dev gcv.h #apt-get install libxml2-dev libxml/parser.h #apt-get install librsvg2-dev librsvg/rsvg.h #apt-get install libdjvulibre-dev libdjvu/ddjvuapi.h #apt-get install libopenexr-dev ImfCRgbaFile.h #apt-get install libjasper-dev jasper/jasper.h #apt-get install libwmf-dev libwmf/api.h #apt-get install libjpeg62-dev libjpeg.la
libjpeg62-devをインストールするとlibjpeg.laが/usr/lib/以下にインストールされます。私の環境では他のlibjpegではlibjpeg.laはインストールされませんでした。
さらにImageMagickをコンパイルする際に必要になるパスにリンクをはっておきます。
#ln -s /usr/lib/x86_64-linux-gnu/libfreetype.la /usr/lib/libfreetype.la
これでコンパイルは通るのでconfigure && make && make installでOKです。
コンパイルとインストールは
./configure --disable-openmp --disable-opencl && make && make install
でOKです。
~~追記~~
ImageMagickはOpenMPが使われているので、pthreadを使ったマルチスレッドアプリケーションではOpenMPを無効にしないといけません(pthreadとOpenMPを両方使用することは多くのプラットフォームでサポート外なため)。
インストールするとライブラリは/usr/local/lib/以下に、ヘッダファイルは/usr/local/include/以下にインストールされます。なので、通常のライブラリ、インクルードパスにリンクをはっておきます。
#ln -s /usr/local/lib/libMagickWand.so.4.0.1 /usr/lib/libMagickWand.so.4 #ln -s /usr/local/lib/libMagickCore.so.4.0.1 /usr/lib/libMagickCore.so.4 #ln -s /usr/local/include/ImageMagick /usr/include/ImageMagick
これでコンパイルと実行が可能になっているはずです。しかし、私の環境では最新のImageMagickでも、configureでOpenMPとOpenCLを無効にしても先述のエラーは消えませんでした。
そこでImageMagickのソースを調査し、デバッグを試みてみました。エラー箇所は
imgproc: magick/semaphore.c:262: LockSemaphoreInfo: Assertion `semaphore_info != (SemaphoreInfo *) ((void *)0)' failed.
のようにmagick/semaphore.cの262行目です。ここではassert(3)が呼び出されていました。どうやらassert(3)の引数が偽なためにabortしているようです。そこで、assert(3)の引数をみると
assert(semaphore_info != (SemaphoreInfo *) NULL);
となっています。つまりsemaphore_infoがNULLなために起こっているエラーなのです。semaphore_infoはLockSemaphoreInfo()の引数です。LockSemaphoreInfo()を呼び出している箇所でNULLならLockSemaphoreInfo()を呼び出さないように改変すれば解決するかもしれません。とってつけたような話ですが、試してみる価値はありそうです。
LockSemaphoreInfo()を呼び出している箇所はwand/wand.cにあります。このソースにあるLockSemaphoreInfo()の呼び出しをif文でかこってやります。
if (wand_semaphore != (SemaphoreInfo *)NULL) (void) LockSemaphoreInfo(wand_semaphore);
さらに、DestroySemaphoreInfo(), UnlockSemaphoreInfo()の呼び出しもif文でかこって、NULLを引数にして呼び出さないように改変しました。
改変後のソースとオリジナルのソースをdiff(1)した結果です。
--- wand.org.c 2010-12-05 08:12:08.000000000 +0900 +++ wand.c 2011-08-01 21:03:28.696000048 +0900 @@ -78,7 +78,10 @@ if (wand_semaphore == (SemaphoreInfo *) NULL) AcquireSemaphoreInfo(&wand_semaphore); - LockSemaphoreInfo(wand_semaphore); + + if (wand_semaphore != (SemaphoreInfo *)NULL) /* 追加 */ + (void) LockSemaphoreInfo(wand_semaphore); /* 追加 */ + if ((wand_ids == (SplayTreeInfo *) NULL) && (instantiate_wand == MagickFalse)) { wand_ids=NewSplayTree((int (*)(const void *,const void *)) NULL, @@ -87,7 +90,10 @@ } id++; (void) AddValueToSplayTree(wand_ids,(const void *) id,(const void *) id); - UnlockSemaphoreInfo(wand_semaphore); + + if(wand_semaphore != (SemaphoreInfo *)NULL) /* 条件追加 */ + UnlockSemaphoreInfo(wand_semaphore); + return(id); } @@ -115,12 +121,18 @@ { if (wand_semaphore == (SemaphoreInfo *) NULL) AcquireSemaphoreInfo(&wand_semaphore); - LockSemaphoreInfo(wand_semaphore); + + if(wand_semaphore != (SemaphoreInfo *)NULL) /* 条件追加 */ + LockSemaphoreInfo(wand_semaphore); + if (wand_ids != (SplayTreeInfo *) NULL) wand_ids=DestroySplayTree(wand_ids); instantiate_wand=MagickFalse; - UnlockSemaphoreInfo(wand_semaphore); - DestroySemaphoreInfo(&wand_semaphore); + + if(wand_semaphore != (SemaphoreInfo *)NULL){ /* 条件追加 */ + UnlockSemaphoreInfo(wand_semaphore); + DestroySemaphoreInfo(&wand_semaphore); + } } /* @@ -147,8 +159,12 @@ */ WandExport void RelinquishWandId(const size_t id) { - LockSemaphoreInfo(wand_semaphore); + if(wand_semaphore != (SemaphoreInfo *)NULL) /* 条件追加 */ + LockSemaphoreInfo(wand_semaphore); + if (wand_ids != (SplayTreeInfo *) NULL) (void) DeleteNodeByValueFromSplayTree(wand_ids,(const void *) id); - UnlockSemaphoreInfo(wand_semaphore); + + if(wand_semaphore != (SemaphoreInfo *)NULL) /* 条件追加 */ + UnlockSemaphoreInfo(wand_semaphore); }
これでプログラムを再度実行してみます。
thread_num:2
[input] src/nature0.png
[output] nature0.png
[input] src/nature1.png
[output] nature1.png
nature0.png was wrote.
nature1.png was wrote.
できてます。ためしにスレッド数を増やしてみましょう。スレッド数を10にしてやってみます(srcディレクトリに画像ファイルを10枚用意します)。
N 10
[input] src/nature3.png
[input] src/nature6.png
[output] nature6.png
[input] src/nature8.png
[output] nature8.png
thread_num:10
[input] src/nature9.png
[output] nature9.png
[input] src/nature1.png
[output] nature1.png
[input] src/nature7.png
[output] nature7.png
[input] src/nature4.png
[output] nature4.png
[input] src/nature0.png
[output] nature0.png
[output] nature3.png
[input] src/nature2.png
[output] nature2.png
[input] src/nature5.png
[output] nature5.png
nature4.png was wrote.
nature6.png was wrote.
nature0.png was wrote.
nature3.png was wrote.
nature2.png was wrote.
nature7.png was wrote.
nature1.png was wrote.
nature8.png was wrote.
nature5.png was wrote.
nature9.png was wrote.
いいですね。10スレッドでも正常に終了しています。