Learn as if you were to live forever

勉強の記録(画像処理,DeepLearning,ときどき英語)

Catlog board2のトラブルシューティング

飼っている猫さんが去年の9月の健康診断で膀胱炎になり(ストラバイト結晶と細菌が検出され)、お薬と治療食で良くなったりまた悪くなったりを繰り返しています。尿の量を測定して変化に早めに気づけたらと思い使ってみることにしました。

Catlog board2は猫のトイレの下にボードを置いておくと、猫さんがトイレに入った時に体重や排泄の記録をアプリに送信してくれる製品です。2024年2月時点でボード(6930円)とアプリ月額(980円)で体調を観察することができます。

設置の際につまづいたところがあり、原因がわかるのに時間がかかってしまったので、対応の仕方をまとめてみました。

トイレを設置する

設置方法に従ってトイレを設置したりWi-Fiを繋ぎCatlog boardがスタンバイ状態になったのを確認できた(「トイレは寝ています( ˘ω˘ )スヤァ…」というメッセージが出ていたのでアプリとボードの通信もできているはず)ので、猫さんがトイレをするのを待ちました。朝6時ごろご飯を食べた後、トイレに行ったので測定されるのを待っていましたが、全く通知が来ません_| ̄|○

ログが記録されない時の確認事項をチェック

まずログが記録されないときの確認事項から設置に問題がないか確認しましたが、この中で特に該当しそうな項目はありませんでした。

設置の仕方に問題はなさそうだったので、あとはWi-Fi設定に何か問題があるのかもと関連記事をひとつづつ読んでいたところ、気になる一文を見つけました。

Aterm WG1200HS4をお使いいただいている場合、ルーターの設定によってはCatlogをご利用いただけない場合がございます。
ルーターをお使いの場合に猫様のデータが更新されない場合には、まずはルーターの設定において『PPPoE』という接続方法が有効になっているかご確認ください。
ルーターの設定を変更する方法については、ルーターのメーカー様またはご契約のプロバイダー様に直接お問い合わせください。

IPv6に対応していなかった

製品名に若干心当たりがあり確認したところ、家で使っているWi-Fiホームルーターです...IPv6に対応したホームルーターなのですがCatlog board2はIPv6に対応していないのだそうです。またWi-Fiの周波数2.4GHz以外の周波数が混在しない方がいいそうです。

無線ルーターの設定項目の記事に従い、ルーターの方式を変更して無事ログを記録することができましたが、高速で安定したWi-FiのためにIPv6や5GHzといった環境を作っていたので、若干残念なところでした。

私はうっかり確認しそびれてしまいましたが、購入前に対応するWi-Fi環境を確認して購入した方が安全です。

 

Catlog board設置方法

https://catlog.zendesk.com/hc/ja/articles/4403650789785-Catlog-Board%E3%81%AE%E3%81%AF%E3%81%98%E3%82%81%E3%81%8B%E3%81%9F

ログが記録されない時チェック項目

https://catlog.zendesk.com/hc/ja/articles/4405555550361--Catlog-Board-%E7%8C%AB%E6%A7%98%E3%81%AE%E3%83%88%E3%82%A4%E3%83%AC%E3%81%8C%E8%A8%98%E9%8C%B2%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AE%E7%A2%BA%E8%AA%8D%E4%BA%8B%E9%A0%85

無線LANルーターの設定項目

https://catlog.zendesk.com/hc/ja/articles/4404902234265-%E7%84%A1%E7%B7%9ALAN%E3%83%AB%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E8%A8%AD%E5%AE%9A%E9%A0%85%E7%9B%AE%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

G検定2023#5

機械学習の知識の整理のためにG検定を受けました。G検定はAI・ディープラーニングの活⽤リテラシー習得のための検定試験で、年に数回(2023年は5回)開催されています。試験はweb形式で自宅で受験できます。

受けようと思ったきっかけ

1年ほど前から機械学習に関する仕事に関わり始め、仕事で必要な部分を適宜勉強していました。物体検出のタスクでも自然言語処理に使われるモデルを使用するなど、体系的に知識が必要と感じ、どう勉強するのが効率的か調べていた時に見つけて受けることにしました。

G検定のシラバス

人工知能の定義や歴史から始まり、ディープラーニングのタスクや手法、AI開発時に気をつけるべき点(開発をスムーズに進める方法、法律や倫理の話など)、活用事例がG検定の出題範囲です。AIを使ったシステムを企画する人から開発する人まで幅広い部門の人が知っておくと良い内容になっています。

試験に出てくる頻度はディープラーニングの手法に関する出題がやや多いので、数学が得意でない人は若干難しいと感じるかもしれません。私の場合は、特にディープラーニングの手法や活用について勉強したかったので、効率よく勉強できて良かったです。

最新のモデルについて知りたいという方には向いていませんが、有名な手法やその背景などを知っておくことで、現在の最新の手法を理解する足掛かりになるような知識を身につけられるというイメージです。

学習期間

7月から10月まで3ヶ月ほど勉強しました。大体1日2時間ほど(累計すると180時間ほど)です。

よく勉強時間は30時間ほどと書かれていますが、ディープラーニングの手法をきちんと理解したいという目的があり、そこに時間をかけたので長めの勉強時間になっています。

使用教材

学習教材は以下のものを使いました。

出題範囲の確認

https://www.amazon.co.jp/dp/4798165948

G検定の出題範囲を一通り学べるテキストです。

この本で出題範囲をざっと確認しました。各章の最後に確認用の問題もついています。必要最低限のことがシンプルに書かれているので、ディープラーニング手法をより深く理解したい場合は、別の詳しい教材が必要だと思います。

ディープラーニングの手法の理解

O'Reilly Japan - ゼロから作るDeep Learning

O'Reilly Japan - ゼロから作るDeep Learning ❷

O'Reilly Japan - ゼロから作るDeep Learning ❹

CNN、自然言語処理強化学習についてきちんと理解したい場合は、ゼロから作るDeepLearningシリーズが良いです。丁寧な解説で理論的に仕組みを理解できます。Pythonを使って実際にディープラーニングを動かす事もできます。

試験対策

最短突破 ディープラーニングG検定(ジェネラリスト)問題集 第2版:書籍案内|技術評論社

試験対策の問題集にも取り組みました。問題のバリエーションがあり、web模試(1回)もついています。問題集の問題は公式問題集よりも深い内容が問われやや難しいです。web模試では現在のディープラーニング事情(海外の規制など)を問う問題もあってさらに難易度が高く感じました。web模試では実際の試験時間と同じ2時間で200問の試験問題を解くので問題を解く時間感覚を体験できます。

‎「G検定対策アプリ」をApp Storeで

参考書を常に持ち歩くのは大変なので、隙間時間の勉強にG検定対策アプリも使いました。苦手分野がわかるのでそこに集中して取り組めました。

公式テキストを0.5ヶ月、その後ゼロから作るディープラーニング(3冊)を2ヶ月、残りの0.5ヶ月を試験対策の勉強にしました。公式テキストと問題集は3回ほど読んで試験対策しました。

2023#5の試験

試験開始前

11月11日の13時から開始の試験を受けました。試験開始前の試験の数日前に届いた案内リンクにアクセスして試験環境の確認などを行います。12時50分から13時10分の間に試験をスタートできます。それ以前またはそれ以降に試験を開始することはできません。スタートボタンを押すと時間がカウントされ後戻りができないので、お手洗いなど試験の準備整えて試験を始めます。

試験問題

試験は191問でした。見直しの時間なしで一問当たり1分半になります。問題は分野ごとに出題されずランダムに問題が出てきます。解答に時間がかかりそうな問題は未解答、解答に自信のない問題は「後で見直す」にチェックを入れて、即答できる問題を一通り回答してしまいます。サクサク進めれば30分は時間が余ったので、残りの時間に未解答の問題に取り組んだり、自信のない問題を考え直したりしましたが、一つ一つ検索する時間はなかったです。私は名前が覚えにくい勾配降下法の種類やモデルの名前とその特徴を表にしたものをまとめた表を準備しました。

web模試で出たような海外の規制などの問題が多かったら厳しいかもと思ったのですが、そういう問題は少なかったです。計算問題や数学的な知識を問う問題も少なかったです(計算問題は2問で、平均、分散、標準偏差の計算ができれば解答できる問題でした)。当日お腹の調子が非常に悪くトイレに何度か行ったので自宅受験で良かったです。

試験後

合否結果

2週間後にメールで合否結果がメールで届きます。

2023#5の合格者の割合は69%でした。

■合否結果
=================
【 合 格 】
=================
総受験者数  5,330名
合格者数   3,662名

シラバス分野別得点率(小数点以下切り捨て)
1.人工知能とは. 人工知能をめぐる動向. 人工知能分野の問題:100%
2.機械学習の具体的手法:91%
3.ディープラーニングの概要:87%
4.ディープラーニングの手法:95%
5.ディープラーニングの社会実装に向けて:77%
6.数理・統計:83%
7.法律・倫理・社会問題:78%

※総合得点率、設問個別の正解・不正解、本試験の合格ライン等は開示しておりません。あらかじめご承知おきください。

自然言語と法律関係が苦手で点数が取れなかったと思われます。

合格認証ロゴ

 名刺などに載せることのできるロゴです。

 合否結果のメールと一緒にダウンロードリンクとパスワードが届きます。

 このブログの冒頭のロゴは合格認証ロゴの一つです。

合格証

 1ヶ月後にPDFで届きます。

オープンバッジ

 合格者のデジタル認定証明。

再受験クーポン

 期間中に再受験チケットを購入すると50%オフクーポンがいただけます。

合格者コミュニティCDLEへの招待

G検定やE検定に合格した人のコミュニティCDLEを活用できます。わからないことがあったときに質問したり、情報共有するコミュニティです。まだ使いこなせていませんが、学生の参加が多いように思いました。

 

G検定は意味があるのか

よく「G検定」で検索すると「意味がない」というキーワードが出てきます。試験に受かるためだけの暗記勉強をした人にとっては意味がないかもしれませんが、AIを正しく活用するためにどんな手法で作られているのか、どんな点に気をつけるべきかをきちんと理解するという目的を持って取り組んだ人にとっては意味のある検定かなと思いました。AIの発展はとても早いのでG検定で問われる内容も時が経つと変わってくるのかなという感じがあります。知識をアップデートするのに時々活用したいなと思います。

画像処理エンジニア検定

画像処理の基礎を体系的に勉強しなおしたいと思い「画像処理エンジニア検定」を受けました。

この検定は画像処理の開発や設計に必要な基本的な知識を評価する検定で、1年に2回(7月と11月)に実施されています。受験の手続きはインターネットで行い、試験は最寄りの試験会場で行います。

出題範囲

出題内容は基礎(画像処理の歴史、カメラの仕組み)、信号処理(ノイズ処理、エッジ検出、画像復元)、検出(特徴量抽出、パターンマッチング、CNN)、知的財産などです。行列計算や統計、フーリエ変換など高校から大学数学の知識が必要な部分もあります。画像処理の基礎が幅広く出題されます。

CG-ARTS | 検定

ベーシックとエキスパートの2つのレベルがあります。出題範囲は大体同じで、エキスパートはベーシックの内容のより深い知識を問われます。合格率はベーシックが約6割、エキスパートは約3割です。

参考書

CG-Artsから出版されている参考書と問題集や過去問を使って勉強します。

CG-ARTS | 書籍・教材

エキスパート用の参考書「ディジタル画像処理」は画像処理の基礎がまとまっていて試験後も仕事の参考書として手元にあると便利な本です。

過去問は過去1年分しかホームページに掲載されていないので、出題のバリエーションのために予想問題集もあるといいです。予想問題を解くことで、苦手分野が分かるし、解説も載っているので、問題の解き方の参考にもなります。

学習について

私は試験勉強は5ヶ月ほどかけて1日2〜3時間程度を目標に勉強しました。

3ヶ月くらいかけて参考書を読み、残りの2ヶ月は問題を解く練習をします。参考書は一度読んだだけでは理解が難しいところもありますが、完全に理解することにこだわらずに一通り読んでみます。早めに読み通してしまったら理解の難しかった部分をもう一度読み返してみると理解できたり、試験問題を解いて間違った部分を読み直すことで理解が深まります(何度も後で振り返ることでその他に理解した知識と繋がって理解できるようになることがよくあります)。

次に1ヶ月かけて予想問題と過去問を時間は気にせず解き、最後の1ヶ月は問題を時間内に解く練習をしました。単願でベーシックの試験時間は60分、エキスパートは80分です。はじめは時間内に解き終わらないし、正解率も低いですが、問題を3周くらい解いていると所要時間の半分くらいで問題が解き終わるようになります。

試験

試験を受けるのは久しぶりだったので、練習のためベーシックを受けてから、エキスパートの試験を受けました。ベーシックは30分ほどで一通り解き終わり、見直しもできましたが、エキスパートは時間がかかってしまった問題があり、見直しが途中までとなってしまいました。迷った問題はサッと飛ばせばよかった...試験後30分で解答を終わる方がいてすごいなと思いました(私はうっかりしているので早く終わっても見直しは必須)。

試験後

試験後3日後に回答が発表されます。

ホームページ上では点数の配分も公開されませんが、自己採点では問題数でベーシックは9割、エキスパートは8割以上正解できていました。久しぶりの試験の雰囲気に緊張して焦ったことによる読み間違えが多かったことと、エキスパートは見直しが半分できなかったので点数が落ちてしまいました。

1ヶ月後に合否結果(合否の結果のみ)が出て、さらに1ヶ月後に合否結果と合格証が送られてきます。

送られてくる合否結果には各分野の正解率や受験者の平均値がグラフで示されます。

感想

普段は仕事に関係のあるところをその都度勉強するという感じで、特定の部分の知識しかなかったのですが、体系的に勉強することで点と点が線で繋がりました。検定の内容は基礎なのでこれを土台にさらに発展した内容は日々情報収集して知識を積み重ねようと思います。

実験環境を変える

今までgoogle colabを使って画像処理の実験をしていたのですが、画像を読み込むときに時間がかかるので、画像をアップロードしないでローカルで実験できるように環境を変えることにしました。

 

私はiMac(M1の一個前のモデル)を使っています。

iMacpythonを使えるように環境設定した時に参考にした記事です。

prog-8.com

 

エディターはVisualStudioCodeにしました。

環境設定が難しそうというのと、google colabと比べてデバッグしにくそうと思っていたので、ローカルの環境を整えていなかったのですが、意外と簡単にpythonを使い始められました。

VScodeデバッグモードを使えば、途中で処理を止めて値を確かめるということもできます。

 

次回からはローカル環境で実験していきます。

マトリックスの色の違い

ちょっとブレイクで、去年18年ぶりに新しいシリーズが公開されましたマトリックスで、画像処理が関する面白い記事を見つけました。

The Matrix looks dramatically different on Hulu versus on HBO Max - echevarria.io

マトリックスを配信しているストリーミングサービスHulu*1とHBOMaxの映像が全然異なるのだそうです。

比較したシーンを見ると、Huluの方が緑ががっていて、HBOMaxの方は暖色系の色味になっています。

私の記憶ではマトリックスといえば緑の(Huluの映像に近い)イメージでしたが、元々1作目はニュートラルで温かみのある色合いで、DVDは初期の色合いが収録されていましたが、2作目の色合いに合わせてBlu-rayでは緑がかった色味に変更されたものが収録されたそうです。

なのでHuluはDVDバージョンを配信していて、HBOMaxはBlu-rayバージョンを配信しているので色味に違いがあるのではという話でした。

そんなこだわりの調整がされているとは知らなかったので、とても面白い深い記事でした(オリジナルバージョンも見てみたいです)。

 

この記事ではマトリックスの映像のもう一つ違いが指摘されていました。

明るさの違いです。Huluはハイライトもシャドー部も自然な表示(ハイライトが白飛びせずシャドー部が引き締まっている)になっていますが、HBOMaxはハイライトが飛んでいてシャドー部も浮いているように見えます。

本当の理由はわかりませんが、HBOMaxの方はHDRモニター用に調整された映像が配信されているためではと言われています。

 

撮影した映像はそのままモニター表示すると、暗くてコントラストの低い見にくい映像です。

モニターで適切に表示するために撮影した信号を、モニター表示用に変換することをガンマ補正といいます。

HDRはHighDynamicRangeの略で従来のモニターよりもより幅広い範囲の輝度を表現できる技術です。従来の方式SDR(StandardDynamicRange)で最大300nitの明るさを表現できていたのに対し、HDRテレビの最大輝度は1000nitまでと言われています(今のところ)。

2つの方式で表現できる明るさの範囲に違いがあるので、それぞれのモニターに合わせたガンマカーブを適用しないと適切な表示になりません。

f:id:i_knit_you_purl:20220109152006p:plain

上の図※のようにHDRモニター用のガンマカーブを適用した映像(青線)を、SDRモニターで表示すると300nit以上は白飛びしているように見えてしまいます(オレンジ線)。※例で適当に作ったグラフなので正しいグラフではありません。

HDRモニター用のガンマカーブ(青線)をSDRモニター用に300nitで飽和しないように作ると以下のオレンジのグラフになります。

f:id:i_knit_you_purl:20220109151719p:plain

将来HDRモニターがスタンダードになってくればこういう問題はなくなるはずですが、今はどちらも混在しているのでこういう問題が出てきしまったのかもしれません。

HDRトーンカーブを適用した映像をSDRモニターで見た場合、ハイライトが飛びやすくなってしまうというのは納得できたのですが、シャドー部は共通なのでシャドー部が浮くというところは説明がつかず、ちょっとモヤモヤしました。

*1:現在Huluではマトリックスの配信はしていないそうです。

copyする理由

前回画像処理のコードの中で各処理の途中でデータをコピーしていた理由*1についてです。

 

int型の時

ある変数v1を設定します。

v1=1
print(v1)

printすると設定した値1が表示されます。

別の変数v2にv1を代入します。

v2=v1
print(v2)

printすると代入したv1の値1が表示されます。

その後v1の値を変更すると、v1の変更はv2に影響しません。

v1=3
print(v1)
print(v2)

printするとv1は変更後の値3が、v2は変更する前に代入したv1の値1が表示され、int型の時はv2の値はv1の変更に影響しません。

ここまでは違和感は感じない話だと思います。

リスト型についても同じように実験してみます。

 

list型の時

v1にリスト型で値を設定します。

v1=[1,1,1]
print(v1)

printすると設定した[1,1,1]が表示されます。

別の変数v2にv1を代入します。

v2=v1
print(v2)

printすると代入したv1の値[1,1,1]が表示されます。

その後、v1の先頭の要素を変更してみます。

v1[0]=2
print(v1)
print(v2)

v1,2をそれぞれ表示してみると、v1もv2も値が変更後のv1の値[2,1,1]に変わっています。

v2への代入後にv1を変更するとリスト型の場合、v2の値も変わってしまいます。

代入後に値を変更したのだから代入前のv2の値が変わるのは不自然に感じたのですが、pythonの変数はオブジェクトのため代入した変数が変わると、その変数が関わるそのほかの変数の値も更新されてしまいます。

 

copyで値が変わらないようにする

代入した値を変更したくない場合はcopyで値を固定します。

変数v1を設定して

v1=[1,1,1]
print(v1)

v2にv1を代入する際にcopyします。

v2=v1.copy()
print(v2)

v1の先頭の値を変更し、v1,2を表示します。

v1[0]=2
print(v1)
print(v2)

v1は変更後の値[2,1,1]、v2は変更前の値[2,1,1]が表示されました。

 

画像のデータも同様のことが起きるので、値の更新が起きてほしくないタイミングでcopyしていました。

raw現像する

前回はraw画像がJPEG画像とはかけ離れていて、いろいろな処理が必要ということがわかりました。

今回はrawpyで得られる情報から画像処理を行い、人が見た時に近い画像を出力してみます。

 

今回もMOIZさんの記事とオーム社から出版されている「デジカメの画像処理*1」の本を参考にしました。

uzusayuu.hatenadiary.jp

 

以下は画像処理のパイプラインをまとめました*2

 

ベイヤー配列の確認

一つづつ処理を見ていく前にベイヤー配列の順番を確認しておきます。

各補正をかけるときに画素によって補正量が異なることがあるためです。

ベイヤー配列はrawpyのraw_patternで確認できます。

raw_pattern=raw.raw_pattern
print(raw_pattern)

以下のような結果が返ってきました。

[[0 1]

 [3 2]]

0〜3の値が何を意味するかはrawpyのcolor_descで確認できます。

print(raw.color_desc)

'RGBG'という答えが返ってきます。

0がR、1がG、2がB、3がGを指します。この指示に沿ってRGBを並べてみると以下のような配列になります。rawpyのdocumentにも書いてありますが、Gの信号が厳密には違う信号のカメラもある(Rの隣のGとBの隣のGの感度が厳密には異なる?)ので区別して書いているそうです。この記事の中ではRの隣のGのことをGr、Bの隣のGのことをGbと呼ぶことにします。

人の目は緑に敏感に反応する特性があるため、ベイヤーセンサーはGの画素を多く配列して解像を高めています。

 

各補正を見ていきます。

ブラックレベル補正

フォトダイオード*3の暗電流*4により固定パターンのノイズ(インパルスノイズ)が発生し、光が全く入ってこない場合に得られる信号が0以上になることがある。そうすると黒い被写体を撮影した時に、黒を黒として表現できないため、光が入ってこない時の信号が0になるように補正します。

ブラックレベルはrarpyのblack_level_per_channelで調べることができます。

各カメラメーカーで使っているセンサーは違うのでメーカーごとにこの値は違うと思います。

rawデータの読み込み後*5にブラックレベルを取得します。

black_level = raw.black_level_per_channel
print(black_level)

そうすると、[512,512,512,512]という値が出てきます。

ブラックレベルは各チャンネル(R,Gr,B,Gb)同じ補正で良さそうです。

ブラックレベルを引く前に、引く前の画像の最大・最小値を確認します(引き過ぎる可能性があるため)。

import numpy as np
print(np.min(raw_image),np.max(raw_image))

491, 9421という値が出てきました。最小値が491なので512引いてしまうと負の値になってしまうので、引きすぎないようにする必要があります。

各チャンネルにブラックレベル補正します。

img_bl = raw_array.copy()
h=raw.sizes.raw_height
w=raw.sizes.raw_width
for y in range(0, h, 2):
    for x in range(0, w, 2):
       img_bl[y + 0, x + 0] -= min(img_bl[y+0,x+0],black_level[bayer_pattern[0, 0]]) # R
       img_bl[y + 0, x + 1] -= min(img_bl[y+0,x+1],black_level[bayer_pattern[0, 1]]) # Gr
       img_bl[y + 1, x + 0] -= min(img_bl[y+1,x+0],black_level[bayer_pattern[1, 0]]) # Gb
       img_bl[y + 1, x + 1] -= min(img_bl[y+1,x+1],black_level[bayer_pattern[1, 1]]) # B

ブラックレベルを引きすぎないようにブラックレベルが信号値より大きければクリップするようにしました。

念の為、ブラックレベルを引いた後の画像の最大・最小値を確認します。

print(np.min(raw_bl),np.max(raw_bl))

0,8909という値が出てきました。引きすぎも起きていないし、最大値もきちんと512引かれています。

補正前後の画像を並べて表示します。

import cv2
max_signal=np.max([np.max(raw_array),np.max(img_bl)])
show_img=cv2.hconcat([raw_image/max_signal,img_bl/max_signal])
plt.imshow(show_img)

補正前(左)は暗部が浮いていましたが、補正後(右)の方が暗部が締まって見えます。

(左)BlackLevel補正前 (右)BlackLevel補正後

デモザイク

色を識別するために輝度を識別できるセンサーの上に赤、緑、青のカラーフィルターを市松模様に並べるセンサーのことをベイヤーセンサーといいます。

 

各画素は赤、緑、青のいづれかの値しか取ることはできないので、存在しない色をデモザイク処理で補完する。


デモザイク処理は解像に影響するとても重要な処理で、さまざまな補完方法があります*6が、今回は一番簡単な処理で補完してみます。

隣接する画素の平均で補完しました(コード長いです)。

img_dms=np.zeros*7
for y in range(h):
   for x in range(w):
       if y%2 ==0 and x%2 == 0:
           # R
           img_dms[y,x,0]=img_bl[y,x]
          # G
           if x+1<w:
               img_dms[y,x,1]=(img_bl[y,x+1]+img_bl[y,x-1])/2
           else:
           img_dms[y,x,1]=img_bl[y,x-1]
           # B
           if y+1>h and x+1<w:
               img_dms[y,x,2]=(img_bl[y-1,x-1]+img_bl[y+1,x-1]+img_bl[y+1,x+1]+img_bl[y-1,x+1])/4
           elif y+1==h and x+1<w:
               img_dms[y,x,2]=(img_bl[y-1,x-1]+img_bl[y-1,x+1])/2
           elif y+1>h and x+1==w:
               img_dms[y,x,2]=(img_bl[y-1,x-1]+img_bl[y+1,x-1])/2
           else:
               img_dms[y,x,2]=img_bl[y-1,x-1]
       elif y%2 ==0 and x%2 != 0:
           # R
           if x+1<w:
               img_dms[y,x,0]=(img_bl[y,x+1]+img_bl[y,x-1])/2
           else:
               img_dms[y,x,0]=img_bl[y,x-1]
           # G
           img_dms[y,x,1]=img_bl[y,x]
           # B
           if y+1<h:
              img_dms[y,x,2]=(img_bl[y-1,x]+img_bl[y+1,x])/2
           else:
              img_dms[y,x,2]=img_bl[y-1,x-1]
       elif y%2 != 0 and x%2 == 0:
           # R
           if y+1<h:
               img_dms[y,x,0]=(img_bl[y-1,x]+img_bl[y+1,x])/2
           else:
               img_dms[y,x,0]=img_bl[y-1,x-1]
           # G
           img_dms[y,x,1]=img_bl[y,x]
           # B
           if x+1<w:
            img_dms[y,x,2]=(img_bl[y,x+1]+img_bl[y,x-1])/2
           else:
            img_dms[y,x,2]=img_bl[y,x-1]
       else:
           # R
           if y+1>h and x+1<w:
            img_dms[y,x,0]=(img_bl[y-1,x-1]+img_bl[y+1,x-1]+img_bl[y+1,x+1]+img_bl[y-1,x+1])/4
           elif y+1==h and x+1<w:
            img_dms[y,x,0]=(img_bl[y-1,x-1]+img_bl[y-1,x+1])/2
           elif y+1>h and x+1==w:
            img_dms[y,x,0]=(img_bl[y-1,x-1]+img_bl[y+1,x-1])/2
           else:
           img_dms[y,x,0]=img_bl[y-1,x-1]
           # G
           if x+1<w:
            img_dms[y,x,1]=(img_bl[y,x+1]+img_bl[y,x-1])/2
           else:
            img_dms[y,x,1]=img_bl[y,x-1]
           # B
           img_dms[y,x,2]=img_bl[y,x]

補完した画像を表示します。

(左)デモザイク補正前 (右)デモザイク補正後

画像が暗くて分かりづらいですが、格子状のアーティファクトは補正されました。

ノイズ除去

センサーには光の入力に応じて変化するランダムノイズがあり、低照度な環境で撮影するほどノイズが多く、画像がざらついた感じになります。ノイズ除去でランダムノイズを除去し滑らかな画像にします。numpyでノイズ処理に関する情報が得られないので今回はこの処理は行いません。

周辺減光補正

センサーに入力する光はレンズの性能によりセンサーの周辺ほど光が減少し、画像が暗くなります。画像の中心と周辺で明るさの差が発生しないよう補正を行います。

この処理もスキップします。

ホワイトバランス

太陽光、蛍光灯、LEDなどの照明は色を持っていますが、人の目では照らしている照明の色を感じることはほとんどありません。それはどのような色の光源の下でも白いものを白く認識できるよう補正が脳の中で行われているため。それと同じ補正をカメラでも行います。

ホワイトバランスはrawpyのcamera_whietbalanceで取得できます。

G信号で割ってゲインにします。

WhiteBalance=np.array(raw.camera_whitebalance)
WBGain=WhiteBalance[:3]/WhiteBalance[1]

それぞれをprintで出力すると以下のようになります。

WhiteBalance[1542 1024 1326 1024]、WBGain[1.50585938 1 1.29492188]

デモザイク後の画像の各チャンネルにWBGainを適用します。

img_wb = img_dms.copy()
for ch in range(3):
img_wb[:,:,ch] *= WBGain[ch]

WBGain前後の画像を並べてみます。

max_signal=np.max([np.max(img_dms),np.max(img_wb)])
show_img=cv2.hconcat([img_dms/max_signal,img_wb/max_signal])
plt.imshow(show_img)

(左)WBGain前 (右)WBGain後

空がより青っぽい色に変化しました。

カラーマトリックス

人の目が色を認識する感度と、センサーが色を認識する感度には違いがあるため、センサーの出力をそのまま表示すると彩度の低い不自然な色合いに見えます。

カラーマトリックスで、カメラで撮影された画像が人の目で見た時と同じように見えるように補正を行います。

ColorMatrixはrawpyのraw_colormatrixで取得できます。

ColorMatrix=raw.color_matrix

ColorMatrixを出力すると3x4のゼロ行列が出てきました。

この行列をかけると画像が真っ黒になってしまうので、補正はかけずにスキップします。

ガンマ補正

センサーは入力した光に対して線形的に信号を出力するが、人の目は入力した光に対して、ガンマ特性(y=x^2.2 x:入力 y:出力)に似た変化をします。

センサーの信号が人の目の特性に合わせる補正を行うので、逆ガンマ補正(y=x^(1/2.2))を行います。

img_gamma = img_wb.copy()
img_gamma=img_gamma/np.max(img_gamma)
img_out=np.zeros([h,w,3])
for ch in range(3):
img_out[:,:,ch] = np.power(img_gamma[:,:,ch],1/2.2)

画像を表示してみます。

だいぶみやすい画像にはなったけれど、彩度やコントラストがJPEG画像と比べて低い感じがします(ColorMatrixをかけられなかったからかな?)。

エッジ強調

画像の輪郭を強調することで、解像感を調整します。

ここもスキップします。

YUV変換

色彩の変化よりも輝度変化に人の目が敏感という特性を利用して、YUVに変換しデータを圧縮してJPEG画像に変換します。

ここもスキップします。

 

rawpyで得られた情報を使って画像処理をしてみました。

パラメータが不明なものもあり、同時に記録していたJPEG画像相当にはできませんでしたが、RAW画像からJPEG画像を作るまでにいろいろな補正が行われて綺麗な画像になっていることがわかりました。

*1:

www.ohmsha.co.jp

*2:デジカメの画像処理のパイプラインをもとに作成しました。実際はもっと色々な処理があると思います

*3:光を電流に変換する機構

*4:光がセンサーに入らない場合でも発生する電荷

*5:rawの読み込みについては一つ前の記事を参照ください

*6:デモザイク処理についてはまた別の時に勉強してまとめてみます

*7:h,w,3