あしのさき

python & computer vision

opencvのグレースケール読み込みの違い

opencvで画像を読み込む際にカラーかグレースケールかを選択できるけど、下記の2つだと値がかわるらしい。

  1. カラー画像をグレースケールオプションで読み込み
  2. カラー画像として読み込んでからグレースケール化


確認するコードは例えばこんな感じで

import cv2
img_1 = cv2.imread('test.jpg', flags=cv2.IMREAD_GRAYSCALE)
img_2 = cv2.imread('test.jpg', flags=cv2.IMREAD_COLOR)
img_2 = cv2.cvtColor(img_2, cv2.COLOR_BGR2GRAY)
diff_img = img_1 - img_2
print(diff_img.max()) 

cv2.IMREAD_GRAYSCALEを使うと環境によって出力が変わるらしいので
厳密にやるならcv2.cvtColorが安全。

あとimreadのflagsを数字で指定してたけど、ちゃんと定義があったの書いとく。

img_1 = cv2.imread('test.jpg', flags=cv2.IMREAD_GRAYSCALE)
img_2 = cv2.imread('test.jpg', flags=cv2.IMREAD_COLOR)
img_3 = cv2.imread('test.jpg', flags=cv2.IMREAD_ANYCOLOR)
img_4 = cv2.imread('test.jpg', flags=cv2.IMREAD_ANYDEPTH)
img_5 = cv2.imread('test.jpg', flags=cv2.IMREAD_UNCHANGED)

こういう小ネタ知っとかないと意外なところで躓いたりするので侮れない。

レモンコンペとよくある落とし穴

コンペの終了からだいぶ時間が過ぎてしまいましたが、折角なのでSIGNATEで開催されたレモンコンペについて振り返りと、すったもんだあった話を残しておこうかと思います。悲しいかな、このコンペであったトラブルは画像認識プロジェクト初期とかに何もわからずぶち抜きそうな人もいそうなのでどこかにちゃんと記録しといて欲しい。

 ちなみに私の成績はステージ1:87位(スコア0.9593629)→ステージ2:121位(スコア0.4640810)と悲しき末路を辿りました。なので私のモデルについては最後に少しだけ触れる程度にしておきます。

コンペ概要

2021年2月1日から3月31日までの期間で開催されたコンペで、画像からレモンの等級を分類するタスクでした。レモンを台座に乗せて撮影する装置で画像を取得しているので背景は安定しており、学習データも1000枚程度と取り組みやすかったと思います。 

signate.jp

データセットの不備

 すったもんだ第一段ですが、開始直後にデータセットの不備が発覚します。実はこのレモン画像データセットでは同一個体のレモンの向きを変えて複数回撮影しており、その同一個体のレモン画像がtrain,testそれぞれに含まれておりLeakしていました。

ついでにこのデータセットのファイル名は撮影した時間がそのままファイル名となっており、train,testをまとめて時系列準にソートすれば同一個体がすぐにわかる上に、レモンの等級ごとにまとめて撮影をしていたようで、撮影時間からどの等級であるかも判断できるようになっていました。このあたりは下記のフォーラムが詳しいです。

https://signate.jp/competitions/431/discussions/20210203161035-24160
そういうこともあり、開始数日でリーダーボードのスコア1が達成されるなどお祭り騒ぎ状態でした。

ルールの変更

これはさすがにアカンということで最終評価用のレモン画像は再度取り直すことになったようです。1ステージでは同一個体は分けたtrain,testデータセットを再配布して、そのtestデータに対しての予測スコアをリーダーボード上で表示、締切日に提出するモデルを選択する。後日アップロードされる新しい最終評価用データセットに対して予測した結果を提出する2ステージ型のコンペに変更されました。あとこのルール変更のタイミングで最初は禁止されていたアンサンブルや疑似ラベルの使用も認められましたが、最終的にはそんなのはどうでもいい事態が発生します。

 最終評価データセット

 最終評価データセットがモデル作成時にはまったくわからないので、この時点でshakeの神様がチラチラと顔を覗かせているのはkagglerの皆様方であればわかるかと思います。実際にフォーラムでもその旨の議論がありました。

https://signate.jp/competitions/431/discussions/2b

そしてこれがすったもんだ第二段なのですが、最終評価データセットは同じ機材を使われており、確かに背景の映り込み問題や白飛び問題は解決されていました。

ただひとつ色調がまったく違うという点を除いては。

 レモン表面の一部のRGBをステージ1,2で比較してみるとこれくらい違います。

f:id:ashi__no:20210505160117p:plain

レモンの色調

外部からの光によって色が変わったのか、カメラの不調またはなんらかのフィルタを入れたのかはわかりませんが、画像全体の色調が変わっています。この画像の変わりようもあり、ステージ2でのスコアは1stでも0.67889と、ステージ1で300人以上が0.9以上を叩き出していた状態からはとんでもない差となりました。フォーラムでステージ1と2の相関を出している方がいらっしゃいましたが、レモンコンペでは0.278494という大shakeでした。(ちなみに鰹節コンペは0.996970)

https://signate.jp/competitions/431/discussions/12

 他人事でもない

 今回のコンペの事象から学ぶべき、画像認識プロジェクト落とし穴としては

1.train,testに同一個体は混ざっていないか(Leakはないか)

2.trainと最終実装時に撮影される画像は常に見た目が同じかどうか

という2点かと思います。これだけ見ると当たり前すぎるんですが、実際にコンペで起こっていますし、各会社内のプロジェクトでも普通にあると思います(大事になる前に誰かが気づいて軌道修正していると思いますが)

特に2は長期に渡ってモデルを運用する際には撮影機器や環境が変更されて画像の見た目が変わる可能性が高いため、モデルのロバスト性を高めるか、画像の見た目が変わらないように定期的に色調や明るさを校正する必要があります。

今回のコンペはわざと色調が違う画像を入れたのか、それとも意図せず色調が変わったのかわかりませんが、モデルにロバスト性を持たせるくらいであれば撮影機器側で校正作業をするべきかと思います。(校正用のレモン(プラスチック製)みたいなのを用意して色調、明るさ、等級判定が正解するように確認する)

モデルについて

自分が作成したモデルについても少しだけ書いておきます。

efficientnet_b5,mixup,adam,CosineAnnealingWarmRestarts,CrossEntropyLossで3モデルアンサンブル。前処理はレモンを中心として矩形で切り抜き、レモン以外の部分を黒塗りしています。レモンだけを抽出するためにgrabcutとその他調整を細々としていましたがスコアにはそんない効いてなかったです。また類似したレモンをグルーピングして同一fold内に集まるようにGroupKFoldを試したりしましたが、これもそんなに効かずレモンをチェックしまくった時間はいったい・・・と途方に暮れてました。

まとめ

とまぁ、スコアが伸びない中、途方に暮れながらモデルを提出して、2ステージ目のデータセットを確認してさらに絶望するという苦い経験が得られたコンペでした。みんなもちゃんとtarin,testの分け方と実運用時の撮影環境は確認するんだゾ!!ということでおわりです。