質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

多次元配列

1次元配列内にさらに配列を格納している配列を、多次元配列と呼びます。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

3回答

4782閲覧

C言語による、4連結によるラベリング処理

www.

総合スコア1

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

多次元配列

1次元配列内にさらに配列を格納している配列を、多次元配列と呼びます。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

0クリップ

投稿2020/06/27 09:02

編集2020/06/28 07:20

前提・実現したいこと

C言語によるラベリング処理について質問です。現在、あるpgmファイルを読み込み、それを2値化し、pbmファイルとして出力したものを、4連結でラベリング処理し、連結成分の個数をカウントするプログラムを作っています。
pgmファイルを読み込み、それを2値化し、pbmファイルとして出力するところまでは、うまくいっているのですが、4連結でラベリング処理し、連結成分の個数をカウントする部分がよくわかりません。最後のループでラベリング番号を振った配列を表示しているのですが、テスト用の画像をみてもせいぜい、10もないぐらいだと思うのですが、かなり大きな桁が表示されます。また、ルックアップテーブルをどのように詰めれば良いのかわからずじまいです。ご教授よろしくお願いいたします。テスト用の画像は以下のをpgmファイルにして使用しています。
イメージ説明

該当のソースコード

C

1#include<stdio.h> 2#include<string.h> 3#include<stdlib.h> 4 5 6int main(void){ 7 8 FILE *fp, *output; 9 int h, w, m, v, i, j; 10 char type[10]; 11 int stage[256]={}; 12 13 //pgmファイルがNULLだった場合の処理 14 if ((fp = fopen("test.pgm", "rb"))== NULL){ 15 printf("画像が開けません\n"); 16 exit(1); 17 } 18 19 20 fgets(type,10,fp); //画像形式の読み込み 21 fscanf(fp, "%d", &w); //画像の幅 22 fscanf(fp, "%d", &h); //画像の高さ 23 fscanf(fp, "%d", &m); //画像の輝度の階調 24 25 //上記4つの画像情報を出力 26 printf("type=%sw=%d, h=%d, m=%d, ", type, w, h, m); 27 28 //以下for文で1画素ずつ輝度をカウントし、ヒストグラムの元となる 29 //それぞれの輝度値の合計を出力 30 for (i = 0; i <w; i++){ 31 for (j = 0; j <h; j++){ 32 if((v = getc(fp)) != EOF) { 33 stage[v]++; 34 } else { 35 } 36 } 37 } 38 fclose(fp); 39 40 //以下輝度0から輝度の合計をしていき、ある割合になった時、閾値を決定する 41 double pixel;//画素数を保存 42 double pixel_sum;//輝度値から加算して知った画素数を保存 43 double pixel_average;//画素数比率の保存 44 double pixel_before;//直前の画素数比率の保存 45 int number;//閾値の保存 46 47 pixel=w*h;//画素数を計算 48 printf("pixel=%.f\n", pixel); 49 50 for(i=0;i<256;i++){ 51 pixel_sum=pixel_sum+stage[i]; 52 pixel_average=pixel_sum/pixel; 53 if(pixel_average>=0.7){ 54 printf("輝度値=%d 比率=%.4f\n",i-1,pixel_before);//閾値の1つ前の割合を出力 55 printf("輝度値=%d 比率=%.4f\n",i,pixel_average);//閾値の割合を出力 56 printf("閾値=%d\n",i );//閾値を出力 57 number=i;//閾値を保存 58 break; 59 } 60 pixel_before=pixel_average;//求めた割合を1つ前の割合として保存 61 } 62 63 //以下、決定した閾値から2値化画像を出力、2値化画像を2次元配列として格納 64 //4連結、8連結のラベリング処理を行う 65 output = fopen("test.pbm", "w");//白黒画像であるPBMファイルに出力 66 fp = fopen("test.pgm", "rb"); 67 fgets(type,10,fp); //画像形式の読み込み 68 fscanf(fp, "%d", &w); //画像の幅 69 fscanf(fp, "%d", &h); //画像の高さ 70 fscanf(fp, "%d", &m); //画像の輝度の階調 71 72 fprintf(output, "P1\n%d %d\n", w, h);//マジックナンバーはP1、アスキー形式 73 74 int image[w][h];//2値化画像を2次元配列として格納 75 76 for (i = 0; i <h; i++){//imageの初期化 77 for (j = 0; j <w; j++){ 78 image[w][h]=0; 79 } 80 } 81 82 for (i = 0; i <h; i++){ 83 for (j = 0; j <w; j++){ 84 if((v = getc(fp)) != EOF) { 85 if(v>=number){//画素を読み込んだ時、閾値以上か以下かを判定 86 fprintf(output, "0 ");//閾値以上の場合白として出力 87 image[j][i]=0; 88 }else{ 89 fprintf(output, "1 ");//閾値未満の場合黒として出力 90 image[j][i]=1; 91 } 92 } else { 93 } 94 } 95 fprintf(output, "\n"); 96 } 97 98 fclose(fp); 99 fclose(output); 100 101 int image_4[w][h];//4連結用配列 102 int image_8[w][h];//8連結用配列 103 int count_4[(w*h)/2][2];//4連結用ラベリング配列 104 int count_8[(w*h)/2][2];//8連結用ラベリング配列 105 int l4=1,l8=1;//4連結、8連結用の連結成分カウント変数 106 int min=0;//検査時の最小値を保存 107 108 for (i=1;i<(w*h)/2;i++){ 109 count_4[i][0]=i; 110 count_4[i][1]=0; 111 count_8[i][0]=i; 112 count_8[i][1]=i; 113 } 114 115 for (i = 0; i <h; i++){//image_4の初期化 116 for (j = 0; j <w; j++){ 117 image_4[w][h]=0; 118 } 119 } 120 121 122 for (i = 0; i <h; i++){//4連結用 123 for (j = 0; j <w; j++){ 124 if(image[j][i]==1){//対象画素が1,以下は4連結用 125 if(i==0&&j==0){//画素の一番始まり(左上角) 126 image_4[0][0]=l4; 127 l4++; 128 } 129 130 else if(i==0){//対象画素が一番上の辺にあたる場所にある 131 if(image_4[j-1][0]!=0){ 132 image_4[j][0]=image_4[j-1][0]; 133 }else{ 134 image_4[j][0]=l4; 135 l4++; 136 } 137 138 }else if(j==0){//対象画像が一番左の辺に当たる場所にある 139 if(image_4[j][i-1]!=0){ 140 image_4[j][i]=image_4[j][i-1]; 141 }else{ 142 image_4[j][i]=l4; 143 l4++; 144 } 145 146 }else{//上記に当てはまらない全ての画素 147 if(image_4[j-1][i]!=0||image_4[j][i-1]!=0){//左マスまたは上のマスが0ではない 148 149 if(image_4[j-1][i]==image_4[j][i-1]){//左と上のマスが同じラベル番号 150 image_4[j][i]=image_4[j-1][i]; 151 152 }else if((image_4[j-1][i]!=0)&&((image_4[j][i-1]==0)||(image_4[j-1][i]<image_4[j][i-1]))){ 153 //左のラベル番号が0ではなくかつ、左のラベル番号の方が小さい 154 image_4[j][i]=image_4[j-1][i]; 155 count_4[image_4[j][i-1]][1]=image_4[j-1][i]; 156 157 }else{//上のラベル番号が0ではなくかつ、上のラベル番号の方が小さい 158 if((image_4[j][i-1]!=0)&&((image_4[j-1][i]==0)||(image_4[j][i-1]<image_4[j-1][i]))){ 159 image_4[j][i]=image_4[j][i-1]; 160 count_4[image_4[j-1][i]][1]=image_4[j][i-1]; 161 } 162 } 163 164 }else{ 165 image_4[j][i]=l4; 166 l4++; 167 } 168 } 169 } 170 } 171 } 172 173for (i = 0; i <h; i++){ 174 for (j = 0; j <w; j++){ 175 printf("%d ", image_4[j][i]); 176 } 177 printf("\n"); 178} 179for(i=1;i<(w*h)/2;i++){ 180 if(count_4[i][1]!=0) 181 printf("%d %d\n",count_4[i][0],count_4[i][1] ); 182 183} 184 185 186//printf("4連結の連結成分の個数=%d\n",max4); 187return 0; 188 189} 190

試したこと

最後のループでラベリング番号を振った配列を表示し、正しくラベリング番号が振られているかどうかを調べた。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

e-watt

2020/06/28 00:06 編集

・なにはともあれ、image_4[][]を初期化していないのでゴミの値が出るのは必然 ・>pbmファイルとして出力するところまでは、うまくいっているのですが、 そうでもないと思いますよ。 1.fscanf()で諧調を読み込んだ後、改行はまだ読み飛ばされていないので、  その下のループにてgetc(fp)は最初にその改行を拾ってきます。 読み込んだループの後でstage[]をダンプして確認しましょう。 2.pbmへの出力において、入力ファイルを再度オープンしていますが、  そのあとでヘッダの分を(fgetsなりfseekなりで)読み飛ばしていないため、  getc()はとりあえずその辺の文字をとって来ており、結果のpbmのなかみは  かなり期待と異なっているはずです。 ※実はその辺を修正済みだったらすみません。質問を修正済みのコードに更新してください。 ・(閾値の判定についての質問は、元のままでよいようです。改行を読み飛ばさないままだと判断を誤るようでした) ・念のため、入力のpbmファイルそのものも添付した方が良いのではないでしょうか。 ・ラベリングのコードは追っていません。悪しからず。
e-watt

2020/06/28 01:04 編集

・→その後、ちょっとだけ追いました *とりあえず、ご質問のコードは左と上の画素だけ見る「2連結」になっていませんか? *とりあえずは、コードが長くなっても、参考にしたページ通りに書くべきだと思いますよ。  少なくとも、そのページの記述通りに書いたようには見えません。  どこをどう云う理由で変えたかを書いていただ…くよりは、  やはり一度は元のページ通りに書いてみては?     ご呈示のコードでは、どこか間違っていることは確実ですが、「参照ページのどこそことの相違が原因ですね」と回答するのが困難です。 *私は自分なりに参考ページ通りに書いたら期待通りの結果が出ましたが、  ご質問のコードとはかなり違うものになったので回答には書きません。
fana

2020/06/28 03:21

とりあえず,画像を読み込んで二値化がどうの…という前処理の話と,ラベリング処理の話とを分離した状態の質問にできないのでしょうか? ラベリングの話には前段の部分は不要でしょうし,前段の処理が既に間違っているのであれば,後段のラベリング処理の結果を見ていても仕方ない気がしますし. あと,本件の内容とは関係ありませんけども,「参考にしたページ」には > ラベリング処理を行う画像のパターンによってはうまくラベル番号が割り振られない場合があります。 そこは、何とかして(ちょっと説明が難しい部分)うまく割り振られるようにチャレンジしてみて下さい。 と書かれており,アルゴリズムとして完全ではない様子ですよ. (「どうせなら」入力次第で失敗するかもしれないとかいう心配の無い話を最初から実装した方が…という気もしないでもない)
e-watt

2020/06/28 05:09 編集

・もうひとつ、元のコードの本質的な誤りが解かったので追記します。   }else if((image_4[j-1][i]!=0)&&(image_4[j-1][i]<image_4[j][i-1])){   //左のラベル番号が0ではなくかつ、左のラベル番号の方が小さい この判定は、「上のラベル番号がゼロのとき」は避けなければいけません。 「近傍の領域に『割り振られたラベル番号(>0)』のうち、最小のもの」を割り当てる必要があるからです。 質問時点のコードでは、上のラベル番号がゼロのときに条件が偽となり、 当該箇所のラベルの値が更新されず(=ゼロのまま放置され)、すなわち連続していないことになってしまいます。 修正案:(leftおよびupが適切に宣言及び代入されているとして; 元のコード通りには書く気がしません。読むのも面倒でしょ?)   }else if (left != 0 && (up == 0 || left < up)) {   //左のラベル番号が0ではなくかつ、上がゼロなら自動的に左を採用、または左のラベル番号の方が上より小さい ※これまでの点を修正して所期の動作をするようになれば、(「2連結」なので参照ページとラベル番号は  ことなるものの)それなりの結果が出ます。 (修正:)ルックアップテーブルも生成できているようなので、本来の質問をするために、 あなたの考えに基づく詰め直しの手順(のわかる/わからない範囲)を日本語かコードで追加してから質問を継続すればよいと思います。
www.

2020/06/28 07:15 編集

たくさんのご指摘、ありがとうございます。 *とりあえず、ご質問のコードは左と上の画素だけ見る「2連結」になっていませんか? 参考にしているサイトは、ラスタ走査で8連結を行なっていました。しかし、下のマス、右下は読み込んでいなかったです。私はそれを参考に、ラスタ走査で4連結を行うことにしました。そうすると、上と左に当たるため、2つのマスのみを読み込んでいます。 ご指摘があった場所を直していこうと思います。また、何かあったらご報告、ご質問させていただきます。
e-watt

2020/06/28 06:57

>8連結を行なっていました。しかし、下のマス、右下は読み込んでいなかったです。 読み直してみたらそうでした。 説明では左上、上、右上、左の4ピクセルだけ参照しているけど、それを「8連結」と呼んでいるのですね。私はそれを4連結だとばかり思いこみ、手元では説明通りの実装を試していました。失礼しました。
www.

2020/06/28 07:29

ご指摘のあった部分を修正しました。 1.fscanf()で諧調を読み込んだ後、改行はまだ読み飛ばされていないので、  その下のループにてgetc(fp)は最初にその改行を拾ってきます。 この部分なのですが、過去に、stage[]の全要素の合計==w*h(画素数)というのを確認していたので、改行などは含まれていないと考えておりました。 最後から2番目に当たる2重ループ文で、ラベル番号を実際に割り振った画像配列を全て表示しているのですが、なぜかところどころ行の最後、または2番目あたりにかなり大きなラベル番号が表示されます。 e-watt様 可能であれば、手元の実装したもの、8連結のもので構いませんので、お見せいただくことはできますでしょうか。 よろしくお願いいたします。
e-watt

2020/06/28 09:53 編集

回答に(かなり雑な)コード例を貼りました。 詰め直しは参照ページの8連結だと割とシンプルな結果が出て簡単にできたのですが、 4連結だともうひと手間かけないといけないことが解かって時間を食ってしまいました。
e-watt

2020/06/28 10:40

↑↑すでにお気づきかもしれませんが、image_4の初期化部分で < image_4[w][h]=0; - > image_4[j][i]=0; としてください。 回答は長くなってしまったので、「詰め直し」の説明は別枠の回答で追記します。(teratail的に行儀は悪いかもしれない)
e-watt

2020/06/28 11:27

>stage[]の全要素の合計==w*h(画素数) それはwとhのループで回したからと思われます。改行を読んでしまった分 画像の末尾が読まれずに(EOFに到達せずに)終わっているはずです。 stage[0~255]をダンプして、先頭に意図しない 13(\r)や10(\n)が入っていないか確認してみてください。 あるいは、.pgmファイルにおいて諧調数の後に改行が入らずに画素データが続いていたりしますか? 私はCygwinで試していますが、もし別の環境では意図通り読めている(pbmでも左上に余計な「1」などない)のでしたら理由がわかりません。すみません。
www.

2020/06/28 12:23 編集

e-watt様 >stage[]の全要素の合計==w*hの部分はやはり自分の間違えでした。 fgetsで読み飛ばしを行いました。 ルックアップテーブルについて。 丁寧なご回答ありがとうございます。 自分がわからなかったところはまさに、再帰的結合の部分です。 よくよく考えてみれば。。。という感じでした。ありがとうございます。 サンプル画像のほうもやりましたところ、結果も同じだったので問題なかったです。 とりあえず、自分の疑問は解決できました。丁寧なご解説、大変感謝板いたします。
guest

回答3

0

ベストアンサー

ルックアップテーブルの詰め直し

(※以下をもっと簡単にやってしまう方法はあるかもしれません。)

コードは先の回答のものを参照してください。
以下では4連結で走査する場合を説明するので、NEIGHBORを4として(回答に貼ったまま)コンパイルし実行すれば下記の結果を得られるはずです。

入力画像がシンプルすぎると詰め直す必要が生じないので、参照ページの画像を
説明にある8連結ではなく4連結で走査した結果で説明します。
(質問の画像や参照ページの8連結だと走査直後のDstがきれいな昇順となり、
私のコードの「番号を圧縮」の部分だけで足りてしまう。)

念のため、サンプル画像をのソースも記載します。適当なテキストエディタで下記内容のファイルを作成し、test.pgmと名付けてプログラムに食わせてください。
テキストファイルに見えるかもしれませんが、バイナリファイルだと思えば(輝度値90と33の画素が並んだ)立派なバイナリファイルです

txt

1P5 216 15 390 4ZZZZZZZZZZZZZZZZZZZZ!ZZZZZZZZZZZZZZ!!!ZZZZZZZZZZZZZ!!!ZZZZZ!ZZZZZZZ!!!!ZZZZ!!ZZZZ!!!!!!ZZZ!!!ZZZZZ!!!!!ZZZ!!!ZZZZZZ!!!!ZZ!!!ZZZZZZZ!ZZZ!!!!ZZZZZZZZZZ!ZZ!!ZZZZZZZZZZ!!!ZZZZ!ZZZZZZZ!!!!!Z!!!!ZZZZZZZ!!!ZZ!!!!ZZZZZZZZ!ZZZ!ZZZZZZZZZZZZZZZZZZZZZZ

さて、参照ページの画像を4連結で走査した結果は次の通り(ラベル番号は16進数で表示)

console

1_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 2_ _ _ _ 1 _ _ _ _ _ _ _ _ _ _ _ 3_ _ _ 2 1 1 _ _ _ _ _ _ _ _ _ _ 4_ _ _ 2 1 1 _ _ _ _ _ 3 _ _ _ _ 5_ _ _ 2 1 1 1 _ _ _ _ 3 3 _ _ _ 6_ 4 4 2 1 1 1 _ _ _ 5 3 3 _ _ _ 7_ _ 4 2 1 1 1 _ _ _ 5 3 3 _ _ _ 8_ _ _ 2 1 1 1 _ _ 6 5 3 _ _ _ _ 9_ _ _ 2 _ _ _ 7 7 6 5 _ _ _ _ _ 10_ _ _ _ _ 8 _ _ 7 6 _ _ _ _ _ _ 11_ _ _ _ 9 8 8 _ _ _ _ a _ _ _ _ 12_ _ _ b 9 8 8 8 _ c c a a _ _ _ 13_ _ _ _ 9 8 8 _ _ c c a a _ _ _ 14_ _ _ _ _ 8 _ _ _ c _ _ _ _ _ _ 15_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 16Src 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (Srcと書いていますが要はDstの添え字) 17Dst 1 1 3 2 3 5 6 8 8 10 9 10 13 14

ここで、ラベルが{1,2,4}や{3,5,6,7}である領域はそれぞれ一つにまとまるはずです。
ルックアップテーブルを見ると、

  • Dst[1]=1: ラベル1→ラベル1に変更
  • Dst[2]=1: ラベル2→ラベル1に変更
  • Dst[4]=2: ラベル4→ラベル2に変更

と記録されています。
「ラベル4→ラベル2に変更」して、さらに「ラベル2→ラベル1に変更」を適用すると
「ラベル4→ラベル1に変更」できます。
つまり、上の時点では Dst[4]=2 であるものを、Dst[ Dst[4] ]=1 と順次追うことになります。
そして追及は Dst[n] == n となるような n で終了します。(変更先が自分自身となる)
これをDst[]の全要素について実行しているのが当該部分の while() ループです。

このループを実行後、Dst[]は以下のように{1,3,8,10 (13以降は画像に未適用)}にまとまります。

Console

1Dst 1 1 3 1 3 3 3 8 8 10 8 10 13 14 2

上のようにまとまった数字に対し、{1, 2, 3, 4}と連番を振るのが次のループです。
変数seqnが連番で、Dst[]の要素の値 q をどの連番に振りなおすか、を conv[q] に記録します。
このとき、q が初出の値ならconv[q]は未設定(を示す値ー1)なので連番seqnを与え、seqnを次の値(++seqn)にします。
実行後は

Console

1Dst 1 1 2 1 2 2 2 3 3 4 3 4 5 14

となり、前掲の画像のラベルを添え字としてDst[]の値に置き換えれば、説明ページのような1~4で埋められた画像を得ます。

Console

1_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 2_ _ _ _ 1 _ _ _ _ _ _ _ _ _ _ _ 3_ _ _ 1 1 1 _ _ _ _ _ _ _ _ _ _ 4_ _ _ 1 1 1 _ _ _ _ _ 2 _ _ _ _ 5_ _ _ 1 1 1 1 _ _ _ _ 2 2 _ _ _ 6_ 1 1 1 1 1 1 _ _ _ 2 2 2 _ _ _ 7_ _ 1 1 1 1 1 _ _ _ 2 2 2 _ _ _ 8_ _ _ 1 1 1 1 _ _ 2 2 2 _ _ _ _ 9_ _ _ 1 _ _ _ 2 2 2 2 _ _ _ _ _ 10_ _ _ _ _ 3 _ _ 2 2 _ _ _ _ _ _ 11_ _ _ _ 3 3 3 _ _ _ _ 4 _ _ _ _ 12_ _ _ 3 3 3 3 3 _ 4 4 4 4 _ _ _ 13_ _ _ _ 3 3 3 _ _ 4 4 4 4 _ _ _ 14_ _ _ _ _ 3 _ _ _ 4 _ _ _ _ _ _ 15_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

ひとまず説明は以上です。お役に立てれば幸いです。

投稿2020/06/28 10:55

編集2020/06/28 11:46
e-watt

総合スコア84

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

回答が長くなってしまったので、「詰め直し」は別に回答します。
こちらは全般的な参考に、ということで。
なお、コンパイル前に先頭付近のマクロ NEIGHBOR により、4連結、8連結のどちらかを選んで実行できます。

C

1#define NEIGHBOR 4 /*連結数:4か8*/

C

1/* 2 ・マクロの使い方など、かなり行儀が悪い部分がありますがご了承ください。 3 */ 4#include<stdio.h> 5#include<string.h> 6#include<stdlib.h> 7 8#define NEIGHBOR 4 /*連結数:4か8*/ 9 10/* デバッグ用 */ 11static void showIntMatrix(const char* name, const int* array, int width, int height, int line) { 12 printf("%s: %dWx%dh @%d\n", name, width, height, line); 13 for (int i = 0; i < height; i++){ 14 for (int j = 0; j < width; j++){ 15 int v = array[i + j*height]; 16 if (v == 0) { 17 printf("_ "); 18 }else{ 19 printf("%x ", v); 20 } 21 } 22 printf("\n"); 23 } 24} 25#define showImage() showIntMatrix("Source Image", &image[0][0], w, h, __LINE__) 26#define showLabeledImage() showIntMatrix("Labels", &labeled_image[0][0], w, h, __LINE__) 27 28 29/*デバッグ用*/ 30static void showIntArray(const char* caption, const char* name, const int* array, int N) { 31 printf("%s:\n%s ", caption, name); 32 for (int i=1; i<=N; i++){ 33 printf("%2d ", array[i]); 34 } 35 printf("\n"); 36} 37#define showDST(expr) showIntArray(expr, "Dst", dst, ((t)+1)/2) 38#define PartN (14) 39#define showPartOfDST(expr) showIntArray(expr, "Dst", dst, PartN) /*全部出すと冗長なので先頭付近のみ表示*/ 40 41 42int main(void){ 43 44 FILE *fp, *output; 45 int h, w, m, v, i, j; 46 char type[10]; 47 char junk[10]; 48 int stage[256]={}; 49 50 //pgmファイルがNULLだった場合の処理 51 if ((fp = fopen("test.pgm", "rb"))== NULL){ 52 printf("画像が開けません\n"); 53 exit(1); 54 } 55 56 fgets(type,10,fp); //画像形式の読み込み 57 fscanf(fp, "%d", &w); //画像の幅 58 fscanf(fp, "%d", &h); //画像の高さ 59 fscanf(fp, "%d", &m); //画像の輝度の階調 60 fgets(junk,10,fp); //改行を読み飛ばす 61 printf("J[%s]len(%d)\n", junk, strlen(junk)); 62 63 //上記4つの画像情報を出力 64 printf("type=%sw=%d, h=%d, m=%d\n", type, w, h, m); 65 66 //以下for文で1画素ずつ輝度をカウントし、ヒストグラムの元となる 67 //それぞれの輝度値の合計を出力 68 for (j = 0; j <h; j++){ 69 for (i = 0; i <w; i++){ 70 do { 71 if((v = getc(fp)) == EOF) { 72 break; 73 } 74 } while (v == '\r' || v == '\n'); 75 if (v == EOF) 76 break; 77 78 printf("[%d],", v); 79 stage[v]++; 80 81 } 82 printf("\n"); 83 } 84 fclose(fp); 85#if 0 86 for (v=0; v<256; ++v) { 87 printf("%d ",stage[v]); 88 if (v % 16 == 15) { 89 printf("\n"); 90 } 91 } 92#endif 93 94 //以下輝度0から輝度の合計をしていき、ある割合になった時、閾値を決定する 95 double pixel;//画素数を保存 96 double pixel_sum;//輝度値から加算して知った画素数を保存 97 double pixel_average;//画素数比率の保存 98 double pixel_before;//直前の画素数比率の保存 99 int number;//閾値の保存 100 101 pixel=w*h;//画素数を計算 102 printf("pixel=%.f\n", pixel); 103 104 for(i=0;i<256;i++){ 105 pixel_sum=pixel_sum+stage[i]; 106 pixel_average=pixel_sum/pixel; 107 if(pixel_average>=0.7){ 108 printf("輝度値=%d 比率=%.4f\n",i-1,pixel_before);//閾値の1つ前の割合を出力 109 printf("輝度値=%d 比率=%.4f\n",i,pixel_average);//閾値の割合を出力 110 printf("閾値=%d\n",i );//閾値を出力 111 number=i;//閾値を保存 112 break; 113 } 114 pixel_before=pixel_average;//求めた割合を1つ前の割合として保存 115 } 116 117 //以下、決定した閾値から2値化画像を出力、2値化画像を2次元配列として格納 118 //4連結、8連結のラベリング処理を行う 119 /* いや、8連結はやらないよ。*/ 120 output = fopen("test.pbm", "w");//白黒画像であるPBMファイルに出力 121 fp = fopen("test.pgm", "rb"); 122 fgets(junk,10,fp); //ヘッダを読み飛ばす 123 fgets(junk,10,fp); //ヘッダを読み飛ばす 124 fgets(junk,10,fp); //ヘッダを読み飛ばす 125 126 127 fprintf(output, "P1\n%d %d\n", w, h);//マジックナンバーはP1、アスキー形式 128 129 int image[w][h];//2値化画像を2次元配列として格納 130 131 132 for (i = 0; i <h; i++){ 133 for (j = 0; j <w; j++){ 134 do { 135 if((v = getc(fp)) == EOF) { 136 break; 137 } 138 } while (v == '\r' || v == '\n'); 139 if (v == EOF) 140 break; 141 printf("[%d]", v); 142 if(v>=number){//画素を読み込んだ時、閾値以上か以下かを判定 143 fprintf(output, "0 ");//閾値以上の場合白として出力 144 image[j][i]=0; 145 }else{ 146 fprintf(output, "1 ");//閾値未満の場合黒として出力 147 image[j][i]=1; 148 } 149 } 150 printf("\n"); 151 fprintf(output, "\n"); 152 } 153 154 fclose(fp); 155 fclose(output); 156 157 showImage(); 158 159 160 int labeled_image[w][h];//4連結用配列 161 int Label=0; //ラベル番号 162 int NL = ((w*h)+1)/2; //ラベルの最大数 163 int dst[NL+1];//ラベリング配列 ; Srcは書き換えないし連番だから、Dstしか用意しないぜ 164 165 for (i=1;i<=NL;i++){ 166 dst[i] = i; //[1]から[((w*h)+1)/2]にその値を入れるぜ; 167 //ただし質問者様は要素数と末尾の処理を間違ってるっぽい 168 //+ 領域数の最大値は、市松模様に塗り分けられた時の ceil(W*H/2) 3x3マスだと5個まであり 169 } 170//初期化を忘れずに 171 for (i = 0; i <h; i++){// 172 for (j = 0; j <w; j++){ 173 labeled_image[j][i] = 0; 174 } 175 } 176 177/*変態チックなデバッグ用マクロ*/ 178#define _TL_ printf("%d at %d,%d L=%d \n", Label, j, i, __LINE__ ); 179#define _Tm_ printf("%d at %d,%d L=%d \n", min, j, i, __LINE__ ); 180 181/* (x,y)のラベル番号を与える。画像の範囲外に対してはゼロを与える */ 182#define getLabel(x,y) ((0<=(x)&&(x)<w && 0<=(y)) ? labeled_image[x][y] : 0) 183 184/*ゼロでなくminより小さい値であればそれを与える*/ 185#define nonzeroMin(var,min) (((var) != 0 && (var) < (min)) ? (var) : (min)) 186 187 for (i = 0; i <h; i++){ 188 for (j = 0; j <w; j++){ 189 if(image[j][i]==1){ //対象画素が1 190 int up_m = getLabel(j, i-1); //上 4/8連結で共通 191 int left = getLabel(j-1, i); //左 4/8連結で共通 192#if NEIGHBOR == 4 //4連結 193 if (up_m == 0 && left == 0) { //コメントは8連結側を参照されたし 194 labeled_image[j][i] = ++Label; 195//_TL_ 196 }else{ 197 int min = Label; 198 min = nonzeroMin(up_m, min); 199 min = nonzeroMin(left, min); 200 labeled_image[j][i] = min; 201 dst[up_m] = nonzeroMin(dst[up_m], min); //双方ゼロではないので実は普通のmin(a,b)でよい 202 dst[left] = nonzeroMin(dst[left], min); 203_Tm_ 204 } 205#elif NEIGHBOR == 8 206 int up_l = getLabel(j-1, i-1); //左上 207 int up_r = getLabel(j+1, i-1); //右上 208 209 //『』内は参照ページの記述の引用;なるべく書いてある通りに作るぜ 210 //『左上、上、右上、左の画素のラベル番号を参照し、全て0(ゼロ)の場合は』 211 if (up_l == 0 && up_m == 0 && up_r == 0 && left == 0) { 212 labeled_image[j][i] = ++Label; //『最後に割り振った番号+1のラベル番号を割り振ります。』 213_TL_ 214 }else{ 215 //『参照した画素のラベル番号が複数存在した場合は、』 216 /* →実際に「複数」であるかどうかを問う必要はない */ 217 /* ここに来た以上一つ以上のゼロ以外の値があるので、最小値を決定しさえすればよい。 */ 218 int min = Label; /* この時点で、Labelはこれまでに割り振られた値の最大値 */ 219 min = nonzeroMin(up_l, min); 220 min = nonzeroMin(up_m, min); 221 min = nonzeroMin(up_r, min); 222 min = nonzeroMin(left, min); 223 labeled_image[j][i] = min; //(0以外の)『最小の番号を割り振ります。』 224_Tm_ 225 //『用いなかったラベル番号のルックアップテーブルの番号を最小の番号に書き換えます。』 226 /* ループ内でdst[0] は未使用だ(参照しない)からチェックせずにガンガン上書きするぜ*/ 227 /* 用いたところもどうせminで同じ値だから気にせず上書きするぜ*/ 228 dst[up_l] = nonzeroMin(dst[up_l], min); 229 dst[up_m] = nonzeroMin(dst[up_m], min); 230 dst[up_r] = nonzeroMin(dst[up_r], min); 231 dst[left] = nonzeroMin(dst[left], min); 232 } 233#else 234#error bad number for 'NEIGHBOR' 235#endif 236 } 237 } 238 showLabeledImage(); //一行操作するごとに表示する…と、解説ページと似た感じの出力に 239 } 240 241 printf("RAW result (%dx%d):\n", h, w); 242 showLabeledImage(); 243 244 showPartOfDST("DST before"); 245 246/* ルックアップテーブルの詰め替え */ 247 248 int conv[NL+1]; // 249 for (i=1;i<=NL;i++){ 250 conv[i] = -1; //すべて「未定」にする 251 } 252 conv[0] = 0; 253 dst[0] = 0; 254 255 //再帰的結合; ←これは回答者の勝手な命名で、専門用語ではない 256 for (j=1;j<=NL;j++){ 257 while (dst[j] != dst[ dst[j] ]) 258 dst[j] = dst[ dst[j] ]; 259 } 260 261 showPartOfDST("recursed"); 262 263 //番号を圧縮; 説明しないとわからんと思う 264 int seqn = 1; 265 for (i=1;i<=13;i++){ 266 int dstQ = dst[i]; 267 printf("[%2d] dstQ=%2d conv[dstQ]=%2d...",i, dstQ, conv[dstQ]); 268 if (conv[ dstQ ] == -1) { 269 conv[ dstQ ] = seqn; 270 printf("conv[%2d] gets-to %2d", dstQ, seqn); 271 ++seqn; 272 }else{ 273 printf("%19s", ""); 274 } 275 printf(" dst[%2d]<<%2d, dstQ=%2\n", i, conv[dstQ], dstQ); 276 277 dst[i] = conv[dstQ]; 278 } 279 280 showIntArray("Conv:", "conv", conv, PartN); 281 showPartOfDST("after:"); 282/* ルックアップテーブルの詰め替え;ここまで */ 283 284 //ルックアップテーブルに従い結果を書き換え 285 dst[0] = 0; 286 for (i = 0; i <h; i++){ 287 for (j = 0; j <w; j++){ 288 labeled_image[j][i] = dst[ labeled_image[j][i] ]; 289 } 290 } 291 //最終結果を表示 292 showLabeledImage(); 293 294 printf("%d連結の連結成分の個数=%d?\n", NEIGHBOR, dst[Label]); /*使われたラベルの値(最大値)の圧縮結果が領域の数…とは限らなそう*/ 295 296return 0; 297}

投稿2020/06/28 08:46

編集2020/06/28 11:36
e-watt

総合スコア84

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ラベリングの部分を関数化して、2値化画像配列から認識した領域を保持する配列を出力する関数としてデバッグの対象にすればいいのかと思います。ラベリングの問題は単純に解けば迷路探索問題となり、これがスローバージョンのコードになると思います。これを高速化することはできるらしいのですが私はその画像処理の分野の専門家じゃないのでよくわからないです。ネットにあがっているラベリングのコードを一生懸命見てヒントを得るしかないのかと思います。

まずスローバージョンのコードをあげることを考えます。teratailに類似の質問があったと思います。

teratail - Pythonで二重配列を使った問題

もし必要があれば、ファストバージョンのコードを作成することを考えます。ネットにあがっているラベリングのソースコードを見て、わからなければコードを模写するなどしてとっかかりを作ります。

スローバージョンを作るにしろ、ファストバージョンを作るにしろ、テストケースを用意してやる必要があります。テストケースを多数用意してアルゴリズムの正しさを検証することが重要であると考えます。

もしこのサイトで、より有用なアドバイスが欲しいのであれば、自分が使用しているラベリングのアルゴリズムの参考文献かサイトのURLを追記するといいのではないかと思いました。私は画像処理の専門家ではないのですが、もしかしたら画像処理に詳しい御仁の目にとまるかもしれません。がんばってください。

投稿2020/06/27 11:53

編集2020/06/27 11:58
anndonut

総合スコア667

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問