🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
scikit-learn

scikit-learnは、Pythonで使用できるオープンソースプロジェクトの機械学習用ライブラリです。多くの機械学習アルゴリズムが実装されていますが、どのアルゴリズムも同じような書き方で利用できます。

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

機械学習

機械学習は、データからパターンを自動的に発見し、そこから知能的な判断を下すためのコンピューターアルゴリズムを指します。人工知能における課題のひとつです。

コードレビュー

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

Q&A

解決済

3回答

2641閲覧

画像の白黒反転するためになぜ、明るさの二乗平均をとるコードの意味が気になっています。

sequelanonymous

総合スコア123

scikit-learn

scikit-learnは、Pythonで使用できるオープンソースプロジェクトの機械学習用ライブラリです。多くの機械学習アルゴリズムが実装されていますが、どのアルゴリズムも同じような書き方で利用できます。

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

機械学習

機械学習は、データからパターンを自動的に発見し、そこから知能的な判断を下すためのコンピューターアルゴリズムを指します。人工知能における課題のひとつです。

コードレビュー

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

0グッド

1クリップ

投稿2021/03/04 14:43

下記のページのコードをお試して実行してみています。
参考にしたページ

とりわけ、下記コードのわからないポイント1)コメントアウトの文章の意味2)cropしてbrightにx,yでfor文で回して配列を作っている箇所がわかりません。
この2点について、何かお気づきの点や、どう理解したらいいのか、この辺の情報をみてみるといいなど、ありましたらご回答頂けると助かります。

1)コメントアウトの文章の意味

  • 1画素ずつといっていますが、例えば、xが1のときcrop(8,8,16,16)のピクセルがはいるはずです。1画素ずつではないと思っています。
  • 1画素に縮小される範囲の明るさ、というのは、まず、1画素に縮小される範囲を指定する必要があると思いますが、コメントアウト直下のコードがそうなっている気がしていません。
  • なぜ、二乗平均をとると白黒反転したことになるのか。

2)cropしてbrightにx,yでfor文で回して配列を作っている箇所

  • 下記のようにcropしたあとにresizeの方がわかりやすそう。

python

1image = img.crop((left, upper, right, lower)).resize((64, 64))
  • 下記の計算がなぜ、明るさの白黒反転になるのかが理解できていない。
255 - crop.mean()**2 / 255
  • 最後に多次元配列をnp.r_を利用して一次元配列にしているのか。おそらく、scikit learnにインプットするだろうが、アウトプットが何を表しているのかがわからない。

一枚一枚の数字の手書き文字の何を表しているのかがわかっていない。

変数img_testのoutput

array([[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 16., 0., 0., 0., 0., 0., 0., 11., 8., 2., 0., 0., 0., 0., 0., 8., 9., 2., 0., 0., 0., 0., 0., 8., 15., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 16., 0., 0., 0., 0., 0., 0., 0., 9., 0., 0., 0., 0., 0., 0., 0., 8., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 4., 4., 0., 0., 0., 0., 0., 0., 6., 4., 0., 0., 0., 0., 0., 0., 3., 4., 0., 0., 0., 0., 0., 0., 0., 5., 0., 0., 0., 0., 0., 1., 16., 10., 7., 0., 0., 0., 0., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], ... ...

質問対象のコード

python

1#画像の入っているフォルダを指定し、中身のファイル名を取得 2filenames = sorted(os.listdir('handwrite_numbers')) 3#フォルダ内の全画像をデータ化 4img_test = np.empty((0, 64)) 5for filename in filenames: 6 #画像ファイルを取得、グレースケールにしてサイズ変更 7 img = Image.open('handwrite_numbers/' + filename).convert('L') 8 resize_img = img.resize((64, 64)) 9 img_data256 = np.array([]) 10 11#ーーーーーーーーわからないポイント 12 #サイズを更に縮めて配列を作り、sklearnのdigitsと同じ型にする 13 #64画素の1画素ずつ明るさをプロット 14 for y in range(8): 15 for x in range(8): 16 #1画素に縮小される範囲の明るさの二乗平均をとり、白黒反転 17 crop = np.asarray(resize_img.crop( 18 (x * 8, y * 8, x * 8 + 8, y * 8 + 8))) 19 bright = 255 - crop.mean()**2 / 255 20 img_data256 = np.append(img_data256, bright) 21 22 #画像データ内の最小値が0、最大値が16になるように計算 23 min_bright = img_data256.min() 24 max_bright = img_data256.max() 25 img_data16 = (img_data256 - min_bright) / (max_bright - min_bright) * 16 26 #加工した画像データの配列をまとめる 27 img_test = np.r_[img_test, img_data16.astype(np.uint8).reshape(1, -1)] 28#ーーーーーーーーー

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

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

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

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

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

guest

回答3

0

一部だけ.

255 - crop.mean()**2 / 255

これは,
ある8*8サイズの範囲の輝度平均値 0~255 を,255~0 に変換するための処理ですね.
0~255 を 255~0 に(大小関係が逆向きになるように)変換するので,その意味合いで「白黒反転」と述べているのでしょう.

単純に「白黒反転」と言われると,
255 - crop.mean()
みたいな線形な変換を思い浮かべますが,
この例では(何らかの理由なり考えがあって)単純な線形変換ではなくて,ちょっとした変換カーブを噛ませているのだと思います.
(イメージが湧かない場合には,crop.mean()の取り得る各値(0~255)に対する変換結果値がいくつになるのか計算してグラフ化でもしてみれば良いかと)

投稿2021/03/05 01:32

fana

総合スコア11985

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

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

sequelanonymous

2021/03/05 15:44

ありがとうございます! 255 - crop.mean()が白黒反転っていう基本的な知識がなかったことに気づけて助かりました! ただ、個人的にbrightという変数名になっているのもきになっていて、brightっていうのは、1ピクセルあたりの1~16で表現される白黒の明るさの数値をさしていて、最終的には、それを64のピクセル[1]?!にいれてフラットしてアウトプットしてるって言う感じでしょうか? なぜ、flatなのか、64なのか、がきになっています。brightという変数名の他にもっと適切な変数名がないかも模索しています。 ----- [1]: img_test = np.empty((0, 64))
guest

0

ベストアンサー

あまりこのコードは参考にされないほうがよいと思います。

1)コメントアウトの文章の意味

教師データにしているsklearnのload_digitsがサイズ8×8なので、一度64×64にした画像データを、再度8×8に縮小しています。64×64の画像には「小さな8×8画像」が8×8個あります(8ばかりでわかりにくいですが)。その「小さな8×8画像」(8×8個)それぞれを「1画素に縮小される範囲」と呼んで、1画素に縮小しています。結果として、縮小された1画素が8×8個になります。

文章の意味はこのとおりですが、以下で示すように、やっていることの意味はわかりません。

2)cropしてbrightにx,yでfor文で回して配列を作っている箇所

これ全体的に全く意味がないことをやっていると私は思います。要は8×8の画像にして白黒反転させたいだけなので、以下のように記載することで充分です。x,yのループ処理は一切不要です。

new_resize_img = 255 - np.array(img.resize((8, 8)))

なぜ、わざわざ64×64にリサイズしてから8×8に素朴な計算で縮小させているのか、理由が分かりません。

また、なぜ2乗平均をしているのかも理由はわかりません。上記のように単純に255から引くだけでよいと思います。「2乗平均」処理をすることで、画素の明るさが偏ってしまっています。そのため、以降の処理で本来は不要な「正規化処理」をするはめになってしまっています(通常、画像の正規化は単純に255で割るだけです)。

参考: 輝度反転

なお、蛇足ですが、正しい意味での「2乗平均」になっていないようです。

bright = 255 - crop.mean()**2 / 255 # 平均した結果を2乗してしまっています
bright = 255 - (crop ** 2).mean() ** 0.5 # 本来の2乗平均であれば、各要素を2乗して平均する

投稿2021/03/04 23:09

編集2021/03/04 23:20
toast-uz

総合スコア3266

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

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

ikadzuchi

2021/03/05 01:03

2乗(正確には2.2乗)する必要があるのはガンマ補正のためです。 通常、データ上の値は物理的な明るさの1/2.2乗された値で記録されているので、演算する際には2.2乗しないと物理的に意味のない値になり、画素の明るさが偏ります。 64×64にリサイズしてから8×8の計算をしているのは2乗平均するためではないでしょうか(正しく2乗平均されていないですが、正しくできていたなら)
fana

2021/03/05 01:41

回答書いた後に,ほぼ同じような意味合い(線形変換ではない,という旨)のコメントがあることに気づきました.(大変失礼)
toast-uz

2021/03/05 03:20

ikadzuchi様、fana様、コメントありがとうございます。元記事にガンマ補正といった記述は無いので、それを意図したものであるか判別できないと思います。もし意図してのことであれば、64×64に縮小する以前に、最初の画像を8×8分割して処理をするコードを書くべきかと思うのですが、いったん普通に縮小してから二乗平均処理をする理由がよくわかりません。(割り切れない部分はpaddingすればよいです)
fana

2021/03/05 03:34 編集

最終的に欲しいサイズ(8*8)を作る部分は謎ですね.何故一旦64を経るのか?とか. (INTER_AREAか何かで8*8に一発リサイズすりゃよいのではないか?と思える) 2乗している変換処理は「ガンマ補正」と呼べる式であるとは思いますが, ikadzuchi氏がおっしゃるような,何らかの意味のあるガンマ値を考えているのかどうかは不明です. (というか,線形変換だと何か問題あるのか?っていうのも)
toast-uz

2021/03/05 03:41

こちらで勉強しました。 https://qiita.com/yoya/items/122b93970c190068c752 意図してのことであれば、上記記事に書かれているように、途中でゴリゴリやらずに、逆補正・補正を最初と最後に入れて、途中は普通にリニアでresizeするのがよいと思います。また、fana様の最後のコメントにあるように、目的(白黒手書き画像の判別)に必要とは思えません。
fana

2021/03/05 10:29 編集

元々書いた字の(黒い)部分の輝度に多少の揺らぎ(濃い薄い)があっても,同程度のものとして扱ってやろう,ということだろうか? > 2乗 あと,元々の絵で字じゃない部分(白い)部分にちょっとしたレベルのノイズが乗ってたら,それを少しばかり強調したデータにしてやることで,結果として学習結果がちょっとだけそういうノイズに強くなる?のかも?? っていう匙加減なのかなぁ,とか. (そういう前処理的なことを学習機に食わせるデータに対して人間が行うべきなのか? というのは知らないのですが) (輝度を「反転」させる理由もわからぬ) --- ※あ,この処理って,学習の教師画像を作るための処理ではないのですね. 勘違いしてました.このコメントは間違い. 教師画像は別にあって,認識対象の画像をその仕様に合わせているのですね.
fana

2021/03/05 03:52

crop.mean() というのは,要は,8*8にリサイズした際の輝度値 に対応するのでしょうから, これ(平均値)の2乗値を用いる処理は意図通りなのだろう,と. これを指して「二乗平均」と呼んでいる言葉の側が誤り.
toast-uz

2021/03/05 09:44

そもそも、意図した「ガンマ補正」なのであれば、縮小計算した後に、逆補正するべきではないでしょうか?
fana

2021/03/05 10:35 編集

2021/03/05 12:42 のコメントは完全に話を勘違いしてたみたいです. これって,判別する対象の画像データを作る話なんですね.(学習用の教師画像を作る話だと思ってた) 何故かガンマ補正(っぽい処理)を噛ませてから判別処理にかけるという.
sequelanonymous

2021/03/05 11:00

ご回答ありがとうございます。二乗平均という言葉がそもそも違ったんですね、自分でも調べてみました。 ただ、最後の変数のアウトプット(=変数img_testのoutput)が何をさしていて、前半のコードとどうつながっているのかが理解できずでして、もし差し支えなければご回答頂けると嬉しいです。 scikit-learnが用意している学習済みモデルを利用していて、記事内だと下記のようにインプットしています。 ーーーーー #画像データの正解を配列にしておく X_true = [] for filename in filenames: X_true = X_true + [int(filename[:1])] X_true = np.array(X_true) #ロジスティック回帰の学習済みモデルに画像データを入れ、判別 pred_logreg = logreg_model.predict(img_test) #結果の出力 print('判別結果') print('観測:', X_true) print('予測:', pred_logreg) print('正答率:', logreg_model.score(img_test, X_true))
toast-uz

2021/03/05 11:27

最後のimg_testは、読み込んだ手書き数字イメージを変換した8×8のデータを長さ64にフラット化して、それをイメージの分だけ並べた2次元arrayです。
sequelanonymous

2021/03/05 15:19

ありがとうございます! 8×8のデータを長さ64にフラットにしている意味がいまいちわからずにいます。なぜ、64でイメージ分の一次元配列を画像の枚数分並べた二次元配列になってるのか、というのがきになっています!
toast-uz

2021/03/05 22:12

「深層学習を用いないで手書き文字認識を実現してみたい」というテーマの記事だからです。一般的に、深層学習は多次元データをそのまま受け付けられますが、通常の機械学習だと一次元データ×データ数の形式しか受け付けられないものが多いからです。特に「画像」という特徴をとらえて解釈できるのが深層学習の1つのメリットであり、通常の機械学習には無いところです。
sequelanonymous

2021/03/06 03:19

ありがとうございます!下記のようにコードを書き直して確認してみました。 自分のもやもやがもう少し具体的になってきました。 下記のようなアウトプットになりますが、データの順番(index)は画像の質には、関係ない認識であっていますでしょうか? 勝手に私は、indexが画像のピクセルの位置を指していると思っていました。 なので、64のデータにフラットにしていいのだろうか、と思ったりしていました。 ----- ... ... img_data256 = 255 - np.array(img.resize((8, 8))) min_bright = img_data256.min() max_bright = img_data256.max() img_data16 = (img_data256 - min_bright) / (max_bright - min_bright) * 16 .... .... ----- ----- img_data16.astype(np.uint8) array([[ 0, 2, 4, 2, 2, 0, 0, 0], [ 2, 8, 4, 4, 6, 6, 2, 0], [ 0, 8, 2, 0, 0, 16, 4, 0], [ 0, 2, 8, 6, 8, 12, 0, 0], [ 0, 0, 0, 0, 8, 2, 0, 0], [ 0, 0, 0, 2, 6, 0, 0, 0], [ 0, 0, 0, 6, 2, 0, 0, 0], [ 0, 0, 0, 2, 0, 0, 0, 0]], dtype=uint8) ----- img_data16.astype(np.uint8).reshape(1, -1) array([[ 0, 2, 4, 2, 2, 0, 0, 0, 2, 8, 4, 4, 6, 6, 2, 0, 0, 8, 2, 0, 0, 16, 4, 0, 0, 2, 8, 6, 8, 12, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 2, 6, 0, 0, 0, 0, 0, 0, 6, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0]], dtype=uint8)
toast-uz

2021/03/06 03:32

データの順番は、学習データである sklearn.datasets の load_digits と整合がとれている必要があります。
toast-uz

2021/03/06 03:36

そろそろ当初の質問意図「明るさの二乗平均をとるコードの意味は何か?」から、かなり逸脱してきましたので、クローズをお願いします。teratailは質問・回答をセットにしたコンテンツを皆さんが参照することで役立つ場ですので、質問項目から離れた議論をコメント欄でやりとりするのは、あまりよろしくない、と考えています。必要があれば新規で質問を立てるようにお願いします。
ikadzuchi

2021/03/06 16:23

返事遅くなりすいません。 ガンマ補正について、断言したのはまずかったですね。2乗する理由が他に思いつかなかったもので。 > 64×64に縮小する以前に、最初の画像を8×8分割して処理をするコードを書くべき 本当ですね。そもそも最初の縮小の時点でガンマの扱いが正しくなくなってしまうので2段階の縮小は結局意味が無いですね。ちょっと思い違いをしていました。 > 意図した「ガンマ補正」なのであれば、縮小計算した後に、逆補正するべきではないでしょうか? これは求めるデータによりますね。「物理的な明るさに対して線形の値」がほしいのであれば逆補正(0.5乗)は必要ないと思います。0.5乗すると「人間の目で見ていい感じの色」になります。
toast-uz

2021/03/06 22:38 編集

ikadzuchi様、コメントありがとうございます。 後半の件、そもそもsklearnのload_digitsが学習データであり、手書きデータを取り込んだ画像がテストデータとなっており、今回のコードでガンマ補正?していると思われるのは後者のみです。ということは、正しく推論するには、学習データであるload_digitsが元からガンマ補正されていることが必要になると思います。そうなのでしょうか?
ikadzuchi

2021/03/07 04:10

なるほど、そうなりますか。そのあたり深く考えておらず、画像データはガンマ補正されていることもいないこともあるので、今回は変換が必要だったのだろうなくらいの考えです。
sequelanonymous

2021/03/07 06:31

ご回答ありがとうございます!新規で質問させていただきます!
guest

0

参考ページを書いた人じゃないので、誤解しているかもしれませんが、

1画素ずつといっていますが、例えば、xが1のときcrop(8,8,16,16)のピクセルがはいるはずです。1画素ずつではないと思っています。

8x8を一画素として扱うようにしているという意図かと。
8x8のブロックを取り出し、その画素の平均を取り出す。
その平均値を元に、blightに0〜255までの数字を割り当てる

なぜ、二乗平均をとると白黒反転したことになるのか。

二乗平均をとると白黒反転`ではなく、255から引いているからです。
これは、式を具体的な数字を入れてみて考えると良いです。
入力画像はconvert('L')によりグレースケールになっているので、画素は0〜255までになっています。
平均をとっても0〜255の範囲で二乗して255で割ることで16段階の値になります。
それを255から引くことで、反転することになります。

 グレースケール:http://www.igunoss.co.jp/imageproc/imageproc1-2.html#:~:text=RGB%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%BC%E7%94%BB%E5%83%8F%E3%81%AB,%E3%81%8C%E7%99%BD%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82

投稿2021/03/04 16:43

t_obara

総合スコア5488

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

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

sequelanonymous

2021/03/05 10:58

ご回答ありがとうございます。文章の意味は、理解できました。 ただ、最後の変数のアウトプット(=変数img_testのoutput)が何をさしていて、前半のコードとどうつながっているのかが理解できずでして、もし差し支えなければご回答頂けると嬉しいです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問