アプリ版:「スタンプのみでお礼する」機能のリリースについて

OpenCVを使っています。
輪郭抽出のプログラムでうまくいかないところがあり困っています。
もし原因や修正方法をおわかりの方がいらしたらぜひアドバイスいただけると助かります。

11×11ピクセルの画像(CV_8UC3)に、一本 上から下に伸びた輪郭線があります。
この輪郭線を境に白黒に塗りつぶしています。
左上から右へ走査し
「もしピクセルが白なら黒にする」
として
処理された画像は輪郭線で白黒に塗り分けることはできました。
このとき、輪郭線の「頂点」を保存したいのですが、うまくいきません。
頂点を表示させると以下のようになってしまいます。

0,0
0,1
0,2
0,3
:
0,10

どこを直すとうまくいくでしょうか。
=========
while (k < 11){ // k = y軸
tate[k] = k; // y方向
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];

while(j < 11 && cb == -1){ // j = x軸
lap_img->imageData[lap_img->widthStep * k + j * 3] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 1] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 2] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 3] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 4] = 0;

yoko[k] = j; // x方向

if (top == j || top < j){
top =j;
}
j = j + 1;
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];

printf("%d, %d\n", tate[k],top);
j = 0;
k = k + 1;
}
}

A 回答 (4件)

そもそも、何をどうしたいのかが、プログラムから読み取るのが困難です。



・ while(j < 11 && cb == -1){ // j = x軸
のループの中で
j = 0;
k = k + 1;
を実行しています。つまり、
lap_img->imageData[lap_img->widthStep * k + j * 3] = 0;
のjは常に0であり、「左上から右へ走査し」ているつもりが、一番左を縦に走査しています。
ただ、それだと
printf("%d, %d\n", tate[k],top);
でtopの値が変化する理由がわかりません。


本当にこのプログラムの結果なのでしょうか?
質問する際に、実際のプログラムをコピーして貼り付けていますか?
結果、またはプログラムをここに書き込む際に間違えていませんか?



あとは、今回とは関係無いかもしれませんが。
○ 変数cbの型は?
IPLImageのimageDataはchar * 型であり、char が符号付き整数になっていることがよくあります。
そのため
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];
だと、画素の値が255 だと cb = -1 になったりします。
0か255かの判定くらいならまだいいのですが、 画素Aと画素Bのどちらが明るいか、とかの場合には、判定が難しくなります。
( 0と64では64の方が明るいですが、 64 と -1では-1の方が明るい、ということになります)
cbをunsignd charとか、int等の整数にして、255までの正の整数が扱えるようにして
cb =(uchar)( lap_img->imageData[lap_img->widthStep * k + j * 3] );
等と、符号無しcharに型変換するとかしましょう。

○次の処理
lap_img->imageData[lap_img->widthStep * k + j * 3 + 3] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 4] = 0;

lap_img->imageData[lap_img->widthStep * k + (j + 1) * 3 ] = 0;
lap_img->imageData[lap_img->widthStep * k + (j + 1) * 3 + 1] = 0;
と同じであり、x座標 j+1の画素のうち、2つのチャンネルを0にしています。
これは意図した操作なのでしょうか?
このままだと、 j=10 のときに X座標11という範囲外にアクセスします。
C言語の仕様で、範囲外にアクセスすること自体は可能ですが、その結果どうなるかは保証されていません。

○同じく
j = j + 1;
の後で
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];
をしていますが、 j=10のループのときは j=j+1でj=11になり、そのj=11を使って座標を求めようとします。
これも範囲外です。


○このプログラムだと、y座標kの一番左が白でないとその横一列はなにもしないことになります。
それでいいのでしょうか?

○変数topが初期化されていないようですが、それでいいのですか?
例えば
k=0のとき top=6 だと
k=1の境界線が4でもtop=6のままです。

○この例ならwhileではなくforの方がいいのでは?
○ (top == j || top < j) は (top<=j)とも書けます。というより、普通はそうします。
    • good
    • 0
この回答へのお礼

kmeeさま

いつもとても迅速に丁寧なアドバイスをくださいまして、ありがとうございます。
プログラムの処理経過のスクリーンショットを添付致しますので
ご参照いただけますでしょうか。

なお、コードは以下のように修正致しました。
====

tate[11] = { 0};
yoko[11] = { 0};

if (ue == 1 && shita == 1)
{
top = 0;
while (k < 11){ // k = y軸
tate[k] = k; // y方向
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];

while(j < 11 && cb == -1){ // j = x軸
lap_img->imageData[lap_img->widthStep * k + j * 3] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 1] = 0;
lap_img->imageData[lap_img->widthStep * k + j * 3 + 2] = 0;

if (j < 11){
lap_img->imageData[lap_img->widthStep * k + (j + 1) * 3] = 0;
lap_img->imageData[lap_img->widthStep * k + (j + 1) * 3 + 1] = 0;
}

yoko[j] = j; // x方向

if (top <= j){
top =j;
}
j = j++;
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];
printf("%d, %d\n", k, j);
//printf("%d\n", k);
//printf("%d\n", j);
}
cvShowImage("Shirokuro", lap_img);
cvWaitKey(0);

k =k ++;
j = 0;
}
}
=====

お礼日時:2014/12/10 20:36

画像は小さくて、全然わかりません。




lap_img->imageData[lap_img->widthStep * k + (j + 1) * 3 ] = 0;
を直したようですが、そもそもこれは何のために入れているのですか?
範囲外になる、という他にも、ループの最後にある
cb = lap_img->imageData[lap_img->widthStep * k + j * 3];
は、 その前でj++された状態なので、
lap_img->imageData[lap_img->widthStep * k + (j + 1) * 3 ] = 0;
で0にした場所です。ということは、cbは必ず0になります。
つまり
 j=0、cb=-1でループに入る
 x座標0 を0にする
 x座標1 も0にする
 j++
 x座標j(つまりx座標1) の値をcbに代入する。(つまり、cb=0)
 ループ終了
となります


j = j++;

これ、意味わかってますか?
たまたま期待通りに動いているかもしれませんが、それはたまたまであって、常にそうなるわけではありません。

j ++ のように、++を後に付けると
j の値を使う → j=j+1 にする
と動作します。
 j0 = j ++
なら
 oldJ = j ;
 j0 = oldJ ;
 j = j + 1 ;
と等価です。

では、 j= j++ がどうなるか、というと、決まっていません。
 j= oldJ

 j=j+1
が、どちらが先に実行されるか、C言語としては決まっていないのです。
 oldJ = j ;
 j = oldJ ;
 j = j + 1 ;
で実行されれば、 j は一つ増えますが
 oldJ = j ;
 j = j + 1 ;
 j = oldJ ;
だったら、jは前のままです。

単に一つ増やしたいだけなら、 j++ だけ書きましょう。

で、前回も書きましたが、 jを0にしてから始めるのですから、素直に
 for( j=0; j < 11 && cb == -1; j ++ )
にして、ループ後のj++を抜いた方がいいのではないでしょうか?

さらに
 while(j < 11 && cb == -1){ // j = x軸
となっているのを
 for( j=0; j < 11; j ++ ) {
  cb = lap_img->imageData[lap_img->widthStep * k + j * 3];
  if ( cb != -1 ) { break ; }
とすれば、ループ終わりのcb=~が不要になります。
    • good
    • 0
この回答へのお礼

kmeeさま

2度も長い丁寧なコメントをくださいまして、本当にありがとうございました!
間違いがわかり、ようやく、ようやくまともに動きました。
もちろん、ピクセルの座標もまともに取得できるようになりました。
ひとつひとつどこがどうおかしいか、自分で自覚して確認できるアドバイスを
くださいまして、本当にありがとうございました。
もう、毎回ですけれども、本当に感謝しています!
ありがとうございました!!!

お礼日時:2014/12/11 23:40

いちおうねんのため:



j=j++;
でどうなるかは確かに #2 でいわれているように「決まっていない」のですが, 正確にいうと
未定義動作である
と決まっています. 「未定義動作」ですから, 何が起きても規格上は全く問題ありません. たとえプログラムを実行している途中で突然鼻歌を歌い出したとしても文句を言ってはいけないのです.
    • good
    • 0
この回答へのお礼

そうなのですね、知りませんでした。
鼻歌を歌いださないように気を付けます(笑)
いろいろとわかっていなくて、アドバイスいただけるととても助かります。
ありがとうございました!

お礼日時:2014/12/12 00:29

追記します。




まず、やろうとしていることを、日本語(またはあなたが普段使う母国語)で書いてみましょう。
最初はおおざっぱでいいです。
次に「おおざっぱ」の各項目で何をするのか、より細かく書きます。
さらに細かく... と続けて、機械的にC言語の命令に置き換えられる、というくらいまでになったら、C言語に「翻訳」してください。

この設計がちゃんとできていないから、プログラムをどう直していいかもわからないのでは?
うまく動作しないときも、設計図通りに動作していてるなら直すべきは設計図、設計図通りに動作していないなら直すべきはプログラム、と修正箇所の切り分けができます。

この設計図が無ければ、「明らかにおかしい」という箇所しか指摘できません。
指摘できても、修正方法まで指摘できないこともあります。
・j=j++ は「明らかにおかしい」し、意図を推測できたので、修正方法も指摘できました
・+ j * 3 + 3 は範囲外になるという点では「明らかにおかしい」ですが、これが何を意図したものかが理解できないので、修正方法は指摘できません。
・top変数の扱いは、top変数が何を意図したものかがわからないので、指摘も修正もできません。
画像全体で境界線の最も右の座標、なら、このプログラムの通りになりますが
注目y座標での境界線の座標、なら、jのループの前に初期化する必要があります。

コメントが少ないのも、わかりずらい理由の一つです。
先のtopについても、コメントで説明があれば解決します



例えば、設計図からプログラムへは次のような流れになります

輪郭の座標を抽出する

左上から走査して、白→黒になった座標を抽出する

「左上から走査」とは
「左から右へ走査」を上から下まで順番に行う
 ↓
 「上から下まで」とは y座標を0から最大y座標(今回は10)まで繰り返すこと
 ↓
 for( k=0 ; k < 11 ; k ++ ) {
  「左から右へ走査して変化点を探す」
 }
    • good
    • 0
この回答へのお礼

はい、わかりました。
次回からもっとコメントを多く書いておおまかな流れを誰が見てもわかるように残すようにします。
足りないところを補足して考えてくださいまして、ありがとうございました。
毎回 貴重なアドバイスをありがとうございます。

お礼日時:2014/12/12 00:31

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!