発展編

TEOライブラリによるプログラミングも,いよいよ「発展編」で す.発展編の課題は高速化です.始めの課題では,TEOライブラ リ関数を用いずに直接画像データにアクセスすることで,画素アクセス のオーバーヘッドを軽減する高速化について学びます.次の課題では PentiumプロセッサのMMX命令を用いた処理の高速化について学びます.

画像データへの直接アクセス

まず画像データがメモリ上でどのように配置されているのか説明しましょ う.画像は縦と横の大きさを持った2次元のデータです.一方メモリは1 次元配列のような記憶領域です.TEOライブラリでは画像データを左上 の点から順に,図5.22に示す走査順序に従っ て各点のプレーンデータ(図5.22の例では R,G,Bプレーンの画素値)をメモリに格納しています.

画像サイズW×HのRGBカラー画像の点(x, y)のRGB値が格納されているメ モリのアドレス計算は以下のように行います.説明の簡単化のために画 像データが格納されているメモリの先頭アドレスは0であるとします.

Rのアドレス : (W×3)×y+x    
Gのアドレス : (W×3)×y+x+1 (5.17)
Bのアドレス : (W×3)×y+x+2  

TeoGetPixelやTeoPutPixelではこの計算によって,指定された座標のデー タのアドレスを獲得し,画素値へのアクセスを行っています.この方法 はメモリ上に並んだ各画素に順番にアクセスする場合には有効な方法で はありません.この方法では一つの画素にアクセスするびにに乗算が2回, 加算が1または2回行われます.画像サイズW×HのRGBカラー画像の各画 素値を取り出すのに単純計算で乗算が6WH回,加算が5WH回行われること になります.

一方メモリに直接アクセスする場合,次の画素値にアクセスするために はアドレスの加算を1回行うだけでいいので,同じ画像データに対する 演算回数は加算3WH-1回になります.

TEO画像データのメモリの先頭番値にはTEOIMAGE構造体のdataメンバか らアクセスすることが可能です.各点の画素値を順番に表示する例を以 下に示します.

画像データへの直接アクセス
    TEOIMAGE     *img;	
    TEO_UINT8    *ptr;
    int          n;
	
    ptr = (TEO_UINT8) TeoData (img);
    for (n = 0; n < TeoFsize (img); n++) {
      printf ("Pixel value = %d\n", *ptr++);      	
    }
図5.22: 画像データのメモリ上での配置


画像データへの直接アクセス ループの高速化
    TEOIMAGE     *img;	
    TEO_UINT8    *ptr;
    int          n;
	
    ptr = (TEO_UINT8) TeoData (img);
    for (n = TeoFsize (img); n > 0; n--) {
      printf ("Pixel value = %d\n", *ptr++);      	
    }

先ほどのソースを上のように修正すると更に高速になります.違いは for文の書き方です.これだけでなぜ早くなるのでしょう, わかります か? ポイントはループの終了条件です.

アセンブラの知識のある人ならすぐにわかると思いますが,始めのソー スと今回のソースでは終了条件を満たしているかどうかを調べるのにか かる低レベルでの命令数が違います.アセンブラの命令セットの中には あるレジスタに格納されている数が正かどうかを調べる命令があり,高 速な演算が可能です.今回のソースではその命令を積極的に利用するよ うにしています.

MMX命令を用いた高速処理

発展編の最後の課題はMMX命令による高速化です.MMX命令に関する資料, MMX命令を用いた画像処理の例がが少ないことから,ここではそれほど 詳しい説明はできません.もし興味を持ったら詳細を調べて,よく詳し い資料を書いてもらえると幸いです.

MMX命令を使用する利点は単純にいうと複数の画素を一度に処理できる ことです.MMX命令では64ビットレジスタに格納した複数の画素データ を一度に扱うことができるため,8ビットの画像データであれば一度に 8画素を処理することができます.単純に計算するとこれだけで処理時 間がMMX命令を使用しない場合と比較して(1/8)に短縮されるわけです.

MMX命令による加算の例を図5.23に示します. 図5.23(a)はラップラウンドの加算です.ラッ プラウンドで計算するとオバーフロー,アンダーフローした分は無視さ れます.また飽和モードでの計算では,オバーフロー,アンダーフロー を起こした場合,上限または下限に飽和する(図5.23(b)). MMX命令一覧を表5.1に掲載しておく.命令 の詳細は各自で調べて下さい.

(a)(b)
図5.23: 加算の例, (a) ラップラウンド, (b) 符合なし・飽和


表5.1: MMX命令一覧


表5.1を見ておわかりのようにMMX命令はアセン ブラの命令ですので,MMX命令を使ったプログラムを書くためには,ソー スをアセンブラで記述しなければなりません.しかしC言語のソース中 にアセンブラのコードを挿入するインラインアセンブラの機能を使用し てプログラムを書くことができます.これによって,アセンブラで記述 するには面倒な部分はC言語で記述し,高速処理したい画像処理部分を アセンブラで記述することができます.

ここでは,背景差分によって動領域を検出するプログラムを例にC言語 のソース中でMMX命令を使用する方法について説明します.図5.25にソースを示します.メイン関数につい てはいまさら説明することもないと思います.MMX命令を使用する部分 について詳しく説明します.図5.24にMMXレ ジスタの値の変化を示す.

(0) 動領域を検出するためのしきい値.MMX命令で使用するために8画素分の 値を用意する.

(1) 入力画像データ,出力画像データのアドレスを別のポインタに代入しま す.

(2) 画像データにアクセスするためのループ.1回のループで8画素分処理す るのでnの増分(減分?)は8です.前回の課題で説明した理由から,ルー プの終了条件はn>0としています.

(3) 入力画像データへのポインタsrc_ptr1の指すデータをMMX用のレジスタ mm0に代入します.インラインアセンブラは次のフォーマットで記述し ます.

asm ("operand source, destination");

or

asm ("operand source");

特殊なケースとして変数の値をレジスタに代入したりレジスタの値を変 数に代入する場合があります.この命令では,変数src_ptr1の値を64ビッ ト単位でレジスタmm0に代入しています.

この場合次のようなフォーマットを用います.

asm ("operand (\%0), destination"::"type"(変数));

MMXレジスタとして使用できるのはmm0からmm7の8つのレジスタです.

(4) 入力画像データへのポインタsrc_ptr2の指すデータをMMX用のレジスタ mm1に代入します.

(5) mm0レジスタの内容をmm2レジスタにコピーします.

(6) レジスタmm0とレジスタmm1の内容の差分を取り,結果をレジスタmm0に 代入します.psubusbは飽和・符合なしのバイト単位の減算命令を表し ます.飽和とは演算結果がアンダーフローした場合は下限値,オーバー フローした場合は上限値とするものである.この場合,差が正値だった 時のみ差の値が入り,負値だった場合0が入ります.

(7) レジスタmm1とレジスタmm2の内容の差分を取り,結果をレジスタmm1に 代入します

(8) 両方の差分値(mm0, mm1)のorを取ります.これにより,レジスタmm0に 画像データ1と画像データ2の差の絶対値が格納されます.

(9) 差の絶対値としきい値を比較して,差の絶対値がしきい値より大きい場 合は0FFh,そうでなければ0をレジスタmm0に格納します.

(10) 上の結果とレジスタmm2(画像データ2の値が格納されている)とのANDを 取ります.これにより差分値がしきい値より大きい画素には画像データ 2の画素値が,そうでない場合は0が格納されます.

(11) 処理結果を出力画像データにコピーします.MMXレジスタの値を変数に コピーするには次のようなフォーマットを用います.

asm ("operand source, (\%0)"::"type"(変数));

(12) 画像ポインタを8画素分移動します.

(13) MMX命令を使った後のおまじないだと思いましょう.

図5.24: MMX命令によるレジスタ値の変化
#include 
#include 
#include 
	
/* ************************************************************************* */
static void
func_subtract_mmx (unsigned char        *src1,
                   unsigned char        *src2,		   
                   unsigned char        *dst,
                   int                  size) {
  int           n;
  unsigned char	*src_ptr1, *src_ptr2, *dst_ptr;
  unsigned char threshold[8] = {16, 16, 16, 16, 16, 16, 16, 16}; .......... (0)
  
  src_ptr1 = src1; ........................................................ (1)
  src_ptr2 = src2;
  dst_ptr  = dst;  

  for (n = size; n > 0; n -= 8) { ......................................... (2)
    asm ("movq (%0), %%mm0"::"r"(src_ptr1)); .............................. (3)
    asm ("movq (%0), %%mm1"::"r"(src_ptr2)); .............................. (4)
    asm ("movq %mm0, %mm2"); .............................................. (5)
    asm ("psubusb %mm1, %mm0"); ........................................... (6)
    asm ("psubusb %mm2, %mm1"); ........................................... (7)
    asm ("por %mm1, %mm0"); ............................................... (8)
    asm ("pcmpgtb (%0), %%mm0"::"r"(threshold)); .......................... (9)
    asm ("pand %mm2, %mm0"); ............................................. (10)
    asm ("movq %%mm0, (%0)"::"r"(dst_ptr)); .............................. (11)
    src_ptr1 += 8; ....................................................... (12)
    src_ptr2 += 8;
    dst_ptr  += 8;    
  }
  asm ("emms"); .......................................................... (13)
}

/* ************************************************************************* */
int
main (int       argc,
      char      **argv) {
  TEOFILE       *src[2], *dst;
  TEOIMAGE      *srcimg[2], *dstimg;
  int           n;
  
  for (n = 0; n < 2; n++) {
    src[n] = TeoOpenFile (argv[n+1]);
    srcimg[n] = TeoAllocSimilarImage (src[n]);
    TeoReadFrame (src[n], srcimg[n]);
  }

  dst = TeoCreateSimilarFile ("-", src[0]);
  dstimg = TeoAllocSimilarImage (dst);

  func_subtract_mmx ((unsigned char *) TeoData (srcimg[1]),
                     (unsigned char *) TeoData (srcimg[0]),
                     (unsigned char *) TeoData (dstimg),
                     TeoFsize (src[0]));

  TeoWriteFrame (dst, dstimg);

  TeoCloseFile (src[0]);
  TeoCloseFile (src[1]);
  TeoCloseFile (dst);  

  TeoFreeImage (srcimg[0]);
  TeoFreeImage (srcimg[1]);
  TeoFreeImage (dstimg);

  return 0;
}
	
/* ******************************************************** End of mmx.c *** */
図5.25: MMX命令を用いた背景差分プログラム

(a) (b) (c)
図5.26: MMX命令を用いた背景差分, (a) 背景画像, (b) 入力画像, (c) 検出された動画像