こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

より高速な画像の表示法

医療画像(DICOM)のバイナリデータを読み込んで表示させるプログラムを作っているのですが、非常に遅い(約4秒)ので改善したいのです。

おそらく、ループ中での、エンディアンの変換とPixelへの張付けが原因と思うのですが、改善方法が判りません。宜しくお願いします。

BolandC++Builder6,Pentiam4,1Gメモリ,XPの環境です。
DICOMO画像のファイルサイズは約2053kB
画像データは1024*1024の16ビットです。

__________________________________
Byte bb[2097152];
int iImage[512][512];
word c,wData;

fp=fopen("filename","rb");
setvbuf(fp,NULL,_IOFBF,4096*1024*1024);

while(gData!=0xE07F) //グループタグの検索//
fread(& gData,2,1,fp);
while(eData!=0x1000) //エレメントタグの検索//
freadd(& eData,2,1,fp);

fread(&wData,2,4,fp); //空読み//
fread(bb,1,2097152,fp); //画像データ//

fclose(fp);

for(y=0;y<=512;y++){
for(x=0;x<=512;x++){
wData=256*bb[4*x+4096*y]+bb[4*x+4096*y+1];
c=wData*256/4096;
iImage[x][y]=c;
Image->Canvas->Pixels[x][y]=(TColor)((c<<16)|(c<<8)|c);
c=0;
}
}

投稿日時 - 2007-05-18 15:00:03

QNo.3010206

すぐに回答ほしいです

質問者が選んだベストアンサー

原因は幾つか推測できるだろうと思います。
が、ここでやるべきは闇雲にここが怪しいからいじってみよう
という場当たり的な対処ではなく、
きちんとプロファイリングをして、どこがホットスポットなのかを
見極めることだと思います。

もっともC++ Builderだとまともに使えるプロファイラがあるのかどうか
わかりません(無責任ですみません)。
少なくとも無料で使えるものはないかも知れません。

投稿日時 - 2007-05-18 17:20:33

補足

遅くなりましたがご意見を参考にしてclockで処理時間を計測しました
default 3.00s
配列を[y][x] 2.47s
#16#17 2.49s
Canvas->Pixel[x][y]を外し、新たな配列に入れた場合 0.062s
となりました。
Canvas->Pixel[x][y]の処理が遅い原因のようですが、ループ内ではBitmapの画素値の計算だけにして、ループ外でBItmapの配列を直接imageに貼付ける処理を試してようと思いますが、方法が判りません。 

投稿日時 - 2007-05-22 10:57:09

お礼

その後、ダブルバッファの様な処理をさせたところ、0.4sと約7倍速くなりました。貴重なご意見ありがとうございました。

投稿日時 - 2007-05-22 13:20:59

このQ&Aは役に立ちましたか?

7人が「このQ&Aが役に立った」と投票しています

回答(18)

ANo.18

┌色々検索していたら,面白いものを見つけました.
│DICOM ファイルフォーマットのわかりやすい説明があります.

DR研究会誌
pp.189~222 よくわかるDICOM画像の取得方法 ‐自作プログラムによる方法‐
http://www.kit.hi-ho.ne.jp/dr-study-group/naiyou/105.pdf


これを読んで,いくつか疑問点が出てきました.

(1) #5,#14 のお礼欄に「うまくいかない」と書かれていますが,
  どう「うまくいかない」んでしょうか?
  ・画像の形はそれらしいが,階調がおかしい.
  ・似ても似つかない画像が表示される.

(2) ご質問は高速化方法となっていますが,元のプログラムでは
  ちゃんと画像が表示されていたのでしょうか?
  #14 のお礼欄に「表示だけなら … OK」とありますが,
  これは画像ファイルを読んで表示させたということでしょうか?

(3) #14 のお礼欄に「リトルエンディアンのはず」とあるので,
  ファイルのフォーマットはおそらく "Implicit VR Little Endian"
  とやらになるのだと思いますが,その場合グループタグや
  エレメントタグもリトルエンディアンなのでしょうか?

  もしそうだとしたら,グループタグとエレメントタグの検索部分
  には間違いがあります.グループタグは PIXEL_GROUP=0x7FE0,
  エレメントタグは PIXEL_DATA_ELEMENT=0x0010 となっているので,
  これをそのまま (エンディアン反転させずに) 検索すべきです.
  つまり 0xE07F → 0x7FE0,0x1000 → 0x0010 に訂正が必要.
  (#9 のお礼欄の URL の Fig.8 参照.)

(4) 上記 URL (DR研究会誌の方) の204~205ページによると,
  Implicit VR のヘッダは,次の4つの繰り返しだそうです.
  (a) Group Tag (2バイト)
  (b) Element Tag (2バイト)
  (c) Value Length (4バイト)
  (d) Data (Value Length バイト)

  ところで,ご質問のプログラムでは,エレメントヘッダを読んだ
  直後に8バイトの空読みをしています.wData が2バイトなのに
  8バイト読み込んでいるのでアクセス違反になるという問題も
  ありますが,それは置いといて….

  Implicit VR の Value Length は4バイトですが,8バイト読んで
  いるのは単なる4バイトの間違い? それとも8バイトが正しいと
  すると,Explicit VR フォーマットの "OB,OW,SQ,US の場合"
  というのに相当しているんでしょうか?

(5) 画像データより前の Data 部にバイナリデータが入ることは
  ないんでしょうか? もしあるとすると,その中にたまたま
  グループタグやエレメントタグと同じ2バイトがあった場合に
  「偽物」をつかまされてしまいます.

(6) Value Length の値は奇数になることはないんでしょうか?
  グループタグとエレメントタグの検索は2バイト単位で読んで
  いるので,奇数バイトの Data があると検索に失敗する可能性
  があります.

画像ファイル内にある,画像データの本体の開始位置前後数十バイト
の16進ダンプを見せていただけると,色々わかるかもしれません.

投稿日時 - 2007-05-19 02:24:48

補足

お礼の書き込み訂正します。Wordで持ってきて
E07F,1000,574F,0000,2000,0000,......................です。

投稿日時 - 2007-05-21 11:03:52

お礼

お世話になっております。
私の勘違いが判りました。各装置のデータはリトルエンディアンなのですが、画像サーバがビックエンディアンに変換してました。
(1)(3)(4)上記理由によりお騒がせいたしました。
ちなみに画像データ前の16進並びは
E0 7F 10 00 57(W) 4F(O) 00 00 20 00 00 00
画像が22 00 20 00............です。
(2)正常に表示できました。
(5)(6)そういう場合もあるかもしれませんが、CT,MRI,透視,カラー画像のDICOM変換の4つでは2byte区切りです。これらの画像に対応できれば当面は問題ありません。

投稿日時 - 2007-05-21 09:07:11

ANo.17

★あぁ、済みませんでした。
・訂正前⇒Word (*lpBuff)[ 1024 ] = (Word*)bb; ←これを宣言
 訂正後⇒Word (*lpBuff)[ 1024 ] = (Word(*)[1024])bb; ←これを宣言
 でした。

投稿日時 - 2007-05-19 01:45:46

ANo.16

★回答者 No.1 です。
・補足要求、有り難うございます。
 ようやく質問ソースを把握できましたので、繰り返し部分を中心にサンプルを載せます。

サンプル:
int iImage[ 512 ][ 512 ];
Byte bb[ 2 * 1024 * 1024 ];
Word (*lpBuff)[ 1024 ] = (Word*)bb; ←これを宣言
Word wData;

if ( (fp = fopen("filename","rb")) != NULL ){
 /*
 画像データを読み込む
 */
 fclose( fp );
}
for ( y = 0 ; y < 512 ; y++ ){
 for ( x = 0 ; x < 512 ; x++ ){
  wData = lpBuff[ y * 2 ][ x * 2 ] >> 4; ←2ピクセル毎に12Bit→8Bit(4096→256階調)
  iImage[ x ][ y ] = wData;
  Image->Canvas->Pixels[ x ][ y ] = (TColor)(wData * 0x010101); ←グレイスケール変換
 }
}

最後に:
・本当は iImage[x][y] は iImage[y][x] にすべきです。
 一般に C 言語で2次元配列は横方向を次元の低い方で表し、縦方向を高い次元で表します。
 多分、もうすべてを [x][y] と操作するように設計していると思うので今度、プログラムを
 行うときには気をつけて下さい。本当は [y][x] 操作に直すべきだと思っていますが…。
・以上。一通り動作するか、確認をお願いします。

投稿日時 - 2007-05-19 00:55:39

お礼

Word (*lpBuff)[ 1024 ] = (Word*)bb; ←これを宣言
の部分で以下のエラーがでました。
[C++ エラー] E2034 'unsigned short *' 型は 'unsigned short ( *)[1024]' 型に変換できない

投稿日時 - 2007-05-19 01:20:06

ANo.15

>もし「1画素16ビットの1024×1024を、間引きして、
>8ビット256階調の512×512にする」んで正しければ、
>以下のようになる。
間引処理
    wData=*p++;
     ↓
    wData=*p;
    p+=2;

投稿日時 - 2007-05-18 23:05:37

ANo.14

Intel系のCPUを使用していて、画像データが16bitの数値なら
下位8bit, 上位8bitの順に格納されるので、
wData=256*bb[n]+bb[n+1];ではなく
wData=bb[n]+256*bb[n+1];となるのですが?

また
bb[4*x+4096*y]、bb[4*x+4096*y+1]としている所からすると
4バイト毎に頭2byteを取得するという事でしょうか?
4×8bit=32bit ≠ 16bit(?)

Intel系のCPUで画像データが2byte(16bit)の場合であれば
下記の様なコーディングも可能です。
short *p, c, wData;

p=(short *)bb;
for(y=0;y<512;y++){
  for(x=0;x<512;x++){
    wData=*p++;
    c=wData/16; // 256/4096 => 1/16
    iImage[y][x]=c; //?? [x][y]
    Image->Canvas->Pixels[y][x]=(TColor)((c<<16)|(c<<8)|c);//??
    c=0;
  }
}

投稿日時 - 2007-05-18 22:56:55

お礼

回答ありがとうございます。
ご指摘の通り、リトルエンディアンのはずなのですが、それだとうまくいかないのです。どこか2重に処理しつじつまが合っているのでしょうか?表示だけならCT装置と透視装置のDICOMデータで確認してOKなのです。
bb[4*x+4096*y]以下は元画像サイズ1024*1024を512*512にリサイズしています。

投稿日時 - 2007-05-19 00:19:16

ANo.13

>(2)『c = wData * 256 / 4096;』の部分は下位バイトの上位4ビットを
>  取り出して c に4ビットを代入するのですか。→c = ((wData << 8) >> 12); と考えた。

No9の考え通りに下位バイトの上位4bitを出すなら良く考えたらこうかも
c= (bb[4*x+4096*y]>>4) & 0xf;


---------------以下ただのメモ
wData=256*bb[4*x+4096*y]+bb[4*x+4096*y+1];
c=wData*256/4096;

c = (256*bb[4*x+4096*y]+bb[4*x+4096*y+1]) *256/4096;
c = (256*bb[4*x+4096*y] + bb[4*x+4096*y+1]) /16;
c = 16*bb[4*x+4096*y] + bb[4*x+4096*y+1]/16
あれ?合わないぞ?(゜∀。)

投稿日時 - 2007-05-18 19:51:44

お礼

私の質問に付き合っていただきましてありがとうございます。
*256/4096の部分はデータの数値0から4096をRGB256階調に変換している部分です。
手元の本でRGBは256階調でとあったので。

投稿日時 - 2007-05-18 22:57:18

ANo.12

#3 です.

可能ならば,iImage[x][y] のxとyを入れ替えて iImage[y][x] のように
した方が,いくらか速くなる可能性はあると思います.

なぜならば,ご質問のプログラムの二重ループ部分がアクセスしている順番
どおりに iImage[][] の要素がメモリ上に並ぶことになり,キャッシュの効きが
良くなると考えられるからです.(bb[] の方は既にそうなっています.)

現在のままでは iImage[][] のあるメモリ領域はランダムアクセスされているので,
キャッシュはあまり効いていない (むしろキャッシュライン単位で読み書きするので
逆効果になっている) のではないかと思われます.

もっとも,iImage[][] のxとyを入れ替える場合には,#10 のコードも改造が必要です.
(もっと簡単になります.)

投稿日時 - 2007-05-18 18:40:35

ANo.11

★回答者 No.1、No.9 です。
・繰り返し文の最適化は、他の回答者さんのを参考にして下さい。
 私はヘッダ部についてアドバイスします。
 そう思いましたが質問のソースから考えると gData=0xE07F、eData=0x1000 が常に
 入ってしまいませんか?
・検索となっていますが、fp で続けて fread() 関数で読み込んでいますよね。
 検索後にデータを読み飛ばしているようでもないので、ここの部分が良く理解できません。
 アドバイスするにあたり、医療画像(DICOM)のバイナリデータの詳細を教えてくれませんか?
 また補足要求です。何度もすみません。

投稿日時 - 2007-05-18 18:37:47

ANo.10

#3 です.

#5 さんのコードを元に,iImage[x][y] の2次元アクセスをしないようにしたバージョンです.
でも,どれほど効果があるかはわかりません.やはり実行時間を測定しないと….
(Image->Canvas->Pixels[x][y] の2次元アクセスは残したままだし.)


 const Byte *src = bb;
 int *destLine = &iImage[0][0]; // iImage の走査線の左端 iImage[0][y] を指す.
 int *dest;
 int x, y;

 for(y = 0; y < 512; y++) {
  dest = destLine; // == &iImage[0][y]
  for(x = 0; x < 512; x++) {
   wData = (src[0] << 8) | src[1];
#if 1
   // こっちでいいのかな?
   c = wData >> 4;
#else /* 0/1 */
   // ダメなら元のまま.
   c = wData * 256 / 4096;
#endif /* 0/1 */
   *dest = c;
   Image->Canvas->Pixels[x][y] = (TColor)(c * 0x010101);
   src += 4;     // 1画素読み飛ばす.
   dest += 512;   // == &iImage[x + 1][y]
  }
  src += 2048;    // 1行読み飛ばす.
  destLine++;     // == &iImage[0][y + 1]
 }



> (fread() についても.)はどういう意味でしょうか?

fread(),fopen() などはディスク I/O を行うので,途中でエラーが起きる
可能性もあります.それにファイルが壊れていたり,間違ったファイルを
指定したりすると,途中で読み込むべきデータがなくなることもあります.

例えばご質問のプログラムでは,ファイル内にグループタグかエレメントタグの
どちらかがない場合,無限ループします.

投稿日時 - 2007-05-18 18:00:06

ANo.9

★回答者 No.1 です。幾つかお尋ねします。
・いろいろとソースを解読して不明な点、気になった点が出てきたので。
 (1)for 文の x <= 512、y <= 512 は x < 512、y < 512 ですよね?
 (2)『c = wData * 256 / 4096;』の部分は下位バイトの上位4ビットを
  取り出して c に4ビットを代入するのですか。→c = ((wData << 8) >> 12); と考えた。
 (3)1ピクセルは 8 バイトで記録されているのですか?
 (4)画像フォーマットの DICOM を描画するとき、モノクロのグレースケールですか?
 (5)bb[] 配列の画像データの構造をもう少し解説してくれませんか?お願いします。
 以上。補足要求します。→やはりループ内を最適化すべきですね。

投稿日時 - 2007-05-18 17:52:12

お礼

回答ありがとうございます。
1)失礼しました。x<512 or x<=511
2)最小値0最大値4096を256階調でという意味です。
3)2Byte/pixelですDICOMのBitAllocateは16bit
4)その通りです。
5)http://www.nv-med.com/jsrt/pdf/2001/57_11/1316.pdf
を参照してください。

投稿日時 - 2007-05-19 00:05:58

ANo.7

>★fread の部分が遅くなっている気がします。
>・fopen() 後のループである fread() をしない方が良いでしょう。

>freadをせずにメモリの確保しメモリ上で検索するには

メモリ上で検索しても余り意味がない。

Cのライブラリルーチンは内部的にバッファを持っていて、バッファリングされてメモリ上にあるデータは、IO操作せずにメモリ操作しかしない。

freadの関数のオーバーヘッドが多少減るのみで、高速化は期待出来ない。

「スキップするヘッダのサイズが数キロバイトで、freadが数千のオーダーで呼ばれる」とかなら、最適化も充分に意味があるが、「患者氏名」とかが入ってるなら、せいぜい繰り返しても数十回であろう。

投稿日時 - 2007-05-18 16:25:46

お礼

回答ありがとうございます。
freadの回数は2000回オーバーですので、期待はできるかもしれません。
ただ、やり方がよく判らないのです。

投稿日時 - 2007-05-18 18:02:48

ANo.6

ええともう片方はこうかな
wData=256*bb[4*x+4096*y]+bb[4*x+4096*y+1];

//演算回数の削減
a= (x<<2) + (y<<12);
wData= (bb[a]<<8)|bb[++a];

投稿日時 - 2007-05-18 16:16:24

ANo.5

>wData=256*bb[4*x+4096*y]+bb[4*x+4096*y+1];
>c=wData*256/4096;

ここ、何か変ですね。上位、下位で2バイト持って来ておきながら
Wordのデータを256倍して上位バイトを捨て、12ビット分の除算で、下位側の4ビットだけ残してる。

>Image->Canvas->Pixels[x][y]=(TColor)((c<<16)|(c<<8)|c);
最終的には、cが8ビットで、それをRGBの24ビットのR,G,B同じモノクロにしているつもりになっている。

もし「1画素16ビットの1024×1024を、間引きして、8ビット256階調の512×512にする」んで正しければ、以下のようになる。

--------------
Byte bb[2097152];
Byte *p;
int iImage[512][512];
word c,wData;

fp=fopen("filename","rb");
setvbuf(fp,NULL,_IOFBF,4096*1024*1024);

while(gData!=0xE07F) //グループタグの検索//
fread(& gData,2,1,fp);
while(eData!=0x1000) //エレメントタグの検索//
freadd(& eData,2,1,fp);

fread(&wData,2,4,fp); //空読み//
fread(bb,1,2097152,fp); //画像データ//

fclose(fp);

p=bb;
for(y=0;y<=512;y++){
for(x=0;x<=512;x++){
c=*p++;//cは16ビットの上位バイト
iImage[x][y]=c*256+*p;//iImageには16ビット分を格納
Image->Canvas->Pixels[x][y]=(TColor)(c*0x010101);
p+=3;
}
p+=2048;
}
--------------

>c=wData*256/4096;
の意味が良く判らないが、元ソースのまま、同じ動作にするなら
--------------
Byte bb[2097152];
Byte *p;
int iImage[512][512];
word c,wData;

fp=fopen("filename","rb");
setvbuf(fp,NULL,_IOFBF,4096*1024*1024);

while(gData!=0xE07F) //グループタグの検索//
fread(& gData,2,1,fp);
while(eData!=0x1000) //エレメントタグの検索//
freadd(& eData,2,1,fp);

fread(&wData,2,4,fp); //空読み//
fread(bb,1,2097152,fp); //画像データ//

fclose(fp);

p=bb;
for(y=0;y<=512;y++){
for(x=0;x<=512;x++){
wData=*p*256+p[1];
c=wData*256/4096;
iImage[x][y]=c;
Image->Canvas->Pixels[x][y]=(TColor)(c*0x010101);
p+=4;
}
p+=2048;
}
--------------
になる。「c=wData*256/4096;」の意味が理解不可能なので、ここは最適化せずにそのまま残している。

投稿日時 - 2007-05-18 16:15:43

お礼

回答ありがとうございます。

もし「1画素16ビットの1024×1024を、間引きして、8ビット256階調の512×512にする」んで正しければ、以下のようになる。

この解釈でよいのですがうまく動きませんでした。

投稿日時 - 2007-05-18 17:59:58

ANo.4

ごめん 違った c=wData*256/4096; は
単純に上の桁を8bit排除する物なのかな

c=wData>>4;
//排除しないならこうだと思う。(通分)

投稿日時 - 2007-05-18 15:52:38

ANo.3

> setvbuf(fp,NULL,_IOFBF,4096*1024*1024);

4GB のバッファですか!? しかも,メモリが 1GB しかないのに.
これは確保に失敗しているでしょう.

いや,それ以前に,size_t が32ビット以下ならば,
4096 * 1024 * 1024 はオーバーフローして0になっているはずです.
(コンパイル時に警告とかエラーになりませんでしたか?)

いずれにしても setvbuf() はエラーを返しており,
バッファは確保されていないはずです.それが遅さの原因と思われます.
ちゃんとエラーチェックをする習慣をつけてください.
(fread() についても.)

投稿日時 - 2007-05-18 15:47:48

お礼

回答ありがとうございます。
setvbufについて。PCはネットに繋いでいないので、Macから質問しているのですがタイプするときに*1024を1回間違いました。
私のタイプミスで余計なお時間を取らせてしまいました。
(fread() についても.)はどういう意味でしょうか?

投稿日時 - 2007-05-18 17:00:17

ANo.2

うーん
見てぱっと考えつくのは 2の乗数による乗除算を論理演算・ビット演算に置き換えていく事かな
例えば

c=wData*256/4096;
//↓(これは下8bitをちょん切って更に3bitずらす作業だと思う)
c=(wData&0xfff8)<<3;
//型の大きさで0xfff8は適当に。
//手を抜くなら (!((!wData)|0x7))<<3 とか。ステップ数多いから遅いかも

投稿日時 - 2007-05-18 15:36:32

ANo.1

★fread の部分が遅くなっている気がします。
・fopen() 後のループである fread() をしない方が良いでしょう。
 つまり、画像全体が収まるメモリを確保して、その画像ファイルすべてを
 メモリに読み込みます。そして、メモリ上で『グループタグの検索』や
 『エレメントタグの検索』を検索してみます。メモリが多いなら出来るかな。
・あと別の方法で一度、画像データから『グループタグの検索』や『エレメントタグの検索』を
 検索して画像の読み込む位置を別のファイルに保存しておきます。
 そして、その位置ファイルを先に読み込み、fseek() 関数で位置を移動してから
 瞬時に画像データを 2097152 バイト分読み込む方式を取ればもっと高速になります。
 また、メモリも画像データを読み込む 2097152 バイト分で済みます。
・以上。参考に。

投稿日時 - 2007-05-18 15:30:57

お礼

回答ありがとうございます。
freadをせずにメモリの確保しメモリ上で検索するには具体的にどうやるのでしょうか?
DICOMはタグの位置は画像毎に異なる(患者名の長さ等)ようなので後者は難しいです。

投稿日時 - 2007-05-18 15:46:38

あなたにオススメの質問