Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コンピュータビジョン勉強会

eqs
April 28, 2021
73

 コンピュータビジョン勉強会

昔やったOpenCV for Python使って画像処理に入門する勉強会の資料

eqs

April 28, 2021
Tweet

Transcript

  1. Text books • A. Kaehler and G. Bradski,『詳解 OpenCV 3』,

    オライリー・ジャ パン, 2018. • 原田達也,『画像認識 (機械学習プロフェッショナルシリーズ)』, 講談社, 2017. • 奥富正敏ら編, 『ディジタル画像処理』, CG-ARTS 協会, 2015. 2/64
  2. Table of Contents 1. はじめに 2. Numpy 基礎 3. 基本的な画像処理

    • 1 章:概要 • 2 章:OpenCV 入門 • 10 章:フィルタとコンボリューション 4. 物体検出 • 13 章:ヒストグラムとテンプレートマッチング • 14 章:輪郭 3/64
  3. サンプルコードについて • テキスト中のプログラムはすべて C++ で書かれている • 今回使用するコードはそれらを Python 向けに書き直したもの •

    全部は書き直していない • なるべく Pythonic なスタイルにするのと説明の都合により機能が 異なることがある • コード無しの解説や関数の紹介で終わってるところは補足のコ ードを追加してるので,テキストにないコードがいっぱいある ので注意 4/64
  4. 前準備 • Python を Anaconda でインストールする • OpenCV をインストールする $

    conda install -c conda-forge opencv または, $ pip install opencv-python • サンプルコードのリポジトリをダウンロードする https://github.com/eqs/opencv3-book-python.git • 画像処理用の画像をダウンロードする • cdコマンドでimagesに移動してからsh get_images.shを実行 5/64
  5. OpenCV を用いたコンピュータビジョンのタスクに Python を 使用するメリット・デメリット Python を使用するメリット • コンパイル不要なので Try

    & Error がしやすい • 言語の使用難易度が低い(記述が簡潔・メモリ管理が楽) • Python 向けの強力な開発環境の恩恵を受けられる • Python で書かれた他のモジュールとの組み合わせが容易になる Python を使用するデメリット • 上手に書かないとめっちゃ遅くなる • 上手に書いても最適化された C++ の速度には及ばない (リアルタイム性が求められなければ最低限の最適化で OK) • 一部,C++ でしか使えないモジュールがある 7/64
  6. 画像処理プログラミングの環境の選択肢 • 好きなエディタを使う (Vim, Atom, Visual Studio Code, ...) •

    Spyder IDE • MATLAB ライクな Python の統合開発環境 • デスクトップアプリケーションとして動作 • JupyterLab • コード・図・ドキュメントを一元管理できるノートブックを編集 できる統合開発環境 • サーバとして動作し,ブラウザからアクセスして利用できる • Jupyter Notebook の機能拡張版 8/64
  7. なぜ環境が大事か? • 開発環境に関する宗教戦争は不毛だしどうでもいい • e.g. Vim vs. Emacs • 作業内容に合わせて環境は選択すべし

    • 画像処理のプログラミングはデータを見ながら Try and Error の サイクルをひたすら回す作業が多いので,これに合致する環境 を選択するのが望ましい • ムラシゲが JupyterLab でよくやる開発体制 • Jupyter Notebook で試行錯誤しながらアルゴリズムを作る • テキストエディタにコードをコピペ & 整形しながら単体のスクリ プトとして体裁を整える • 関数の使い方を忘れたらヘルプを叩く • Numpy 配列の形状操作の確認をコンソールでやる 11/64
  8. なんで Numpy? • OpenCV for Python では,画像を扱うデータ構造として Numpy が採用されている •

    画像の特定のチャネルや部分画像の切り出し,プロット用のデ ータ整形など,扱い方を理解してないと困る 12/64
  9. Numpy 配列の基本:配列の定義と値の参照 • モジュールのインポート import numpy as np # or

    import numpy • 5 つの整数を要素として持つ配列 a を定義 >>> a = np.array([100, 200, 300, 400, 500]) >>> # 等価なコード: a = np.arange(1, 5 + 1) * 100 • 配列 a の中身を参照する >>> a[3] 400 >>> a[0] 100 13/64
  10. Numpy 配列の基本:2 次元配列 • 2 行 3 列の配列 a を定義

    >>> a = np.array([100, 200, 300], [400, 500, 600]) >>> # 等価なコード: a = np.arange(1, 6 + 1).reshape((2, 3)) * 100 • 配列 a の中身を参照する >>> a[0, 0] 100 >>> a[1, 2] 600 >>> a[0, 1] 200 15/64
  11. Numpy 配列の基本:画像のチャネルをとってくる操作 # 横が 640, 縦が 480 ピクセルの RGB カラー画像に見立てたダミー

    >>> a = np.arange(480 * 640 * 3).reshape((480, 640, 3)) # チャネルの取り出し >>> a[:, :, 0] # R のチャネル >>> a[:, :, 1] # G のチャネル >>> a[:, :, 2] # B のチャネル 17/64
  12. OpenCV とは何か? • 1999 年に Intel 社の Gary Bradski (原著者のひとり)

    が立ち上げた オープンソースのコンピュータビジョンライブラリ • OpenCV のゴールのひとつは,洗練されたビジョンアプリケーシ ョンをすばやく構築するのに役立つ使いやすい CV の基盤を提供 すること • 画像処理に詳しい人がツール的に使う感じ.画像処理の知識が無 いまま使うものではない • ライブラリは C++ で記述されているが,Python, Java, MATLAB 向 けのインタフェースがある • C 言語もサポートされていたが,現在はレガシーコード扱いになっ てるはず • 機械学習や深層学習のライブラリも含まれている 19/64
  13. OpenCV を使うのは誰か? • 用途 • 工場の製品検査システム • 航空地図や市街地図のカメラキャリブレーションと画像結合 • 無人機やロボットの視覚システム

    • 生物画像の自動解析(トラッキング,カウント,方向推定など) • OpenCV は商用製品を開発できるライセンスであり,オープンソ ース化は義務付けられていない 20/64
  14. コンピュータビジョンとは何か? • 特定の目的のために,ディジタルカメラやビデオカメラからの データを変換して何らかの判断を行ったり別の表現にしたりす ること • 例 1: 車載カメラの映像から,進行方向に人がいるか判断する •

    例 2: 顕微鏡画像にうつっている細胞の数を数える • 例 3: カメラの映像から手ブレを補正した画像を作る • コンピュータにはヒトが持つ高度なセンサーも情報処理システ ムも無い • カメラから得られた数値のグリッドから課題を解決する 21/64
  15. コンピュータビジョン:不良設定問題との闘いの日々 襲い掛かる不良設定問題達 • 2 次元画像から 3 次元シーンを復元する問題において,2 次元画 像として投影される 3

    次元シーンは無限に存在する • カメラの熱雑音,レンズ歪み,照明のムラや画像圧縮などによる データの劣化 • 歩行者検出やりたいけど色々な服装の人がいる コンピュータビジョンと向き合う際の基本方針 • 目的に合わせた制約(ソフト的,ハード的)を入れることで問題 を簡単化する • 不要な一般化を避ける・解くべき問題のみに集中する • 不良設定問題を見抜くのもスキル(白旗を揚げるのも選択肢) 22/64
  16. OpenCV のオンラインドキュメント • 最新版 (4.0.0-pre) のチュートリアル • https://docs.opencv.org/master/d9/df8/tutorial_ root.html •

    C++, Java, Python でのコードが読める • イントロダクション • https://docs.opencv.org/master/df/d65/tutorial_ table_of_content_introduction.html • チュートリアルからさらに項目を厳選したもの • プラットフォームごとの環境構築の手順も含まれている • チートシート (C++, OpenCV 2.4 用) • https://docs.opencv.org/3.0-last-rst/opencv_ cheatsheet.pdf • OpenCV のライブラリ全体を集約したチートシート • Python 用ではないので,関数名のチェック用みたいな感じ • 印刷して壁に貼ろう 23/64
  17. OpenCV のリポジトリは opencv と opencv_contrib に分かれ ている • opencv: OpenCV

    チームによって保守されている安定したコー ド • opencv_contrib: コミュニティによって保守・開発されてい る最先端の手法のコード • 深層学習,顔認識,RGB-D 画像,Non-Photorealistic Rendering, Structure from Motion (SfM) 等 24/64
  18. OpenCV for Python の特徴 • 画像を扱うデータ構造として,C++ では Mat クラスを使うが Python

    では Numpy を使う • Numpy の機能で OpenCV の関数を代替できることもある • opencv_contrib のモジュールは Python インタフェースが無 いものもあるので注意 25/64
  19. 関数のドキュメントを読みたいとき • IPython の場合 • 関数名の後ろに? をつけて実行 >>> cv2.getGaussianKernel? •

    Spyder IDE の場合 • 関数名にテキストのカーソルを合わせてから Ctrl + I • IPython 同時に開くのもお k • Jupyter Notebook, JupyterLab の場合 • 関数名にテキストのカーソルを合わせてから Shift + Tab • IPython 同時に開くのもお k 26/64
  20. 初めてのプログラム–写真を表示する import cv2 img = cv2.imread('../images/color/Lenna.bmp') assert img is not

    None, 'Loading image is failed.' cv2.namedWindow('Example 2-1', cv2.WINDOW_AUTOSIZE) cv2.imshow('Example 2-1', img) cv2.waitKey(0) cv2.destroyWindow('Example 2-1') 28/64
  21. 2 つ目のプログラム–動画 動画やカメラの映像に対する処理を行う際は,このプログラムが雛 型になる import cv2 cv2.namedWindow('Example 2-3', cv2.WINDOW_AUTOSIZE) cap

    = cv2.VideoCapture(0) assert cap.isOpened(), 'Cannot open the video.' while True: ok, frame = cap.read() if not ok: break cv2.imshow('Example 2-3', frame) key = cv2.waitKey(33) if key >= 0: break cap.release() cv2.destroyAllWindows() 29/64
  22. OpenCV で画像を読み込み,Matplotlib で表示する: 色チャネルの順番が逆になっているので注意 import cv2 from matplotlib import pyplot

    as plt img_bgr = cv2.imread('../images/color/Lenna.bmp') assert img_bgr is not None, 'Loading image is failed.' assert len(img_bgr.shape) == 3, 'Loaded image is not color' # 画像を BGR から RGB に変換する img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # 変換前の画像を表示 plt.subplot(1, 2, 1), plt.imshow(img_bgr), plt.title('BGR image') # 変換後の画像を表示 plt.subplot(1, 2, 2), plt.imshow(img_rgb), plt.title('RGB image') plt.show() 30/64
  23. 簡単な画像処理をかける例 • 画像をぼかすフィルタを適用する • フィルタの設計に関する詳細は 10 章でやる import cv2 from

    matplotlib import pyplot as plt img_src = cv2.imread('../images/mono/BRIDGE.bmp') assert img_src is not None, 'Loading image is failed.' # 画像をぼかす処理をかける img_dst = cv2.blur(img_src, (9, 9)) plt.subplot(1, 2, 1), plt.imshow(img_src), plt.title('src image') plt.subplot(1, 2, 2), plt.imshow(img_dst), plt.title('dst image') # plt.savefig('filename.png') # plt.savefig('filename.pdf') plt.show() 32/64
  24. 動画をファイルに書き出す import cv2 cv2.namedWindow('Example 2-11', cv2.WINDOW_AUTOSIZE) cap = cv2.VideoCapture(0) assert

    cap.isOpened(), 'Cannot open the video.' width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 映像の横幅を取得 height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 映像の縦幅を取得 fps = 15.0 # FPS は動画ファイルからは自動取得できるけどカメラからは無理? # 動画書き込みを行うクラスのインスタンスを作る writer = cv2.VideoWriter('video.mp4', cv2.VideoWriter_fourcc(*'MJPG'), fps, (width, height)) for k in range(100): ok, frame = cap.read() if not ok: break cv2.imshow('Example 2-11', frame) writer.write(frame) key = cv2.waitKey(33) if key >= 0: break cap.release() writer.release() cv2.destroyAllWindows() 34/64
  25. 画像フィルタリング • 画像を色の値からなる「2 次元配列」ではなく, 「2 変数関数」と 解釈する • 画像フィルタリング:入力画像 I(x,

    y) から新しい画像 I′(x, y) を計算するアルゴリズム • 例 1:ある画像からぼけた画像を生成する • 例 2:ある画像を白と黒のみからなる画像に変換する 35/64
  26. 画像フィルタリングの内容はカーネルによって定義される • 出力画像 I′(x, y) の位置 (x, y) における画素値は入力画像中の位 置

    (x, y) 周辺の画素から計算される I′(x, y) = ∑ i,j∈kernel ki,j · I(x + i, y + j) • 上式中の ki,j を線形カーネル(フィルタ)と呼ぶ • 画像に対してカーネル(線形,非線形問わず)を適用する操作 をコンボリューションと呼ぶ 36/64
  27. 線形カーネルによる画像フィルタリングの例:平滑化 import cv2 from matplotlib import pyplot as plt img_src

    = cv2.imread('../images/mono/BRIDGE.bmp') assert img_src is not None, 'Loading image is failed.' # 画像に 9 x 9 の平均化フィルタを適用する img_dst = cv2.blur(img_src, (9, 9)) plt.subplot(1, 2, 1), plt.imshow(img_src), plt.title('src image') plt.subplot(1, 2, 2), plt.imshow(img_dst), plt.title('dst image') plt.show() 38/64
  28. 閾値処理:ある値より上か下の画素を破棄する # 127 より明るい画素を 255 に,その他の画素を 0 にする th, img_bin

    = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) 0 100 200 0 50 100 150 200 250 src 0 100 200 0 50 100 150 200 250 dst 40/64
  29. 色々な閾値処理 • 大津の 2 値化 • 画素が黒/白の 2 クラスに分類可能とみて線形判別分析をやる •

    適応的閾値処理 • 画像上の位置ごとに異なる閾値 T(x, y) を設定する T(x, y) = ∑ i,j∈kernel ki,j · I(x + i, y + j) • ki,j はカーネル全体で等しい重みかガウス関数のどっちか • 文書の画像に対する閾値処理をいい感じにできる 41/64
  30. 閾値処理の実行結果 0 100 200 0 50 100 150 200 250

    src 0 100 200 0 50 100 150 200 250 threshold (t=127.0) 0 100 200 0 50 100 150 200 250 otsu (t=83.0) 0 100 200 0 50 100 150 200 250 adaptive threshold 42/64
  31. 画像の 1 次微分によるエッジ検出 • 関数 f(x) の微分を計算器で処理する際,以下のような中心差分 で近似することがある: f′(x) =

    lim ∆x→0 f(x + ∆x) − f(x) ∆x ≈ f(x + ∆x) − f(x − ∆x) 2∆x • ディジタル画像を扱う際は ∆x = 1 にとってよいので次のように 書ける: f′(x) = f(x + 1) − f(x − 1) 2 • 画像 I(x, y) は 2 変数関数なので,水平・垂直それぞれについて 微分を定義する: Ix (x, y) = I(x + 1, y) − I(x − 1, y) 2 Iy (x, y) = I(x, y + 1) − I(x, y − 1) 2 43/64
  32. 画像の微分はカーネルを適用する操作として表現できる • 画像の 1 次微分: Ix (x, y) = I(x

    + 1, y) − I(x − 1, y) 2 Iy (x, y) = I(x, y + 1) − I(x, y − 1) 2 • カーネルによる表現: Fx = 1 2    0 0 0 −1 0 1 0 0 0    , Fy = 1 2    0 −1 0 0 0 0 0 1 0    • ノイズの影響を抑えるために周辺画素の微分をとって平均を計 算するフィルタ(Sobel フィルタ) : Fx = 1 2    −1 0 1 −2 0 2 −1 0 1    , Fy = 1 2    −1 −2 −1 0 0 0 1 2 1    44/64
  33. 画像の 2 次微分:Laplacian • 連続関数 f(x, y) の Laplacian ∇2f(x,

    y) = ∂2f ∂x2 + ∂2f ∂y2 • 画像の 2 次微分から Laplacian フィルタを導出する Ixx (x, y) = {I(x + 1, y) − I(x, y)} − {I(x, y) − I(x − 1, y)} = I(x − 1, y) − 2I(x, y) + I(x + 1, y) Iyy (x, y) = {I(x, y + 1) − I(x, y)} − {I(x, y) − I(x, y − 1)} = I(x, y − 1) − 2I(x, y) + I(x, y + 1) =⇒ F =    0 1 0 1 −4 1 0 1 0    45/64
  34. 微分の実行結果 sobel_x = cv2.Sobel(img, ddepth=-1, dx=1, dy=0) sobel_y = cv2.Sobel(img,

    ddepth=-1, dx=0, dy=1) img_lap = cv2.Laplacian(img, ddepth=-1, ksize=3) 0 100 200 0 50 100 150 200 250 src 0 100 200 0 50 100 150 200 250 sobel x 0 100 200 0 50 100 150 200 250 sobel y 0 100 200 0 50 100 150 200 250 laplacian 46/64
  35. 自前で用意した線形カーネルをたたみこむ事も可能 emboss = np.array([[1, 0, 0], [0, 0, 0], [0,

    0,-1]]) img_emb = cv2.filter2D(img, ddepth=-1, kernel=emboss) 0 50 100 150 200 250 0 50 100 150 200 250 src 0 50 100 150 200 250 0 50 100 150 200 250 Emboss 47/64
  36. スパイクノイズの重畳 def apply_noise(img, p=0.05): """ 画像にノイズを重畳する関数 """ # 画素数 npixels

    = img.size random_indices = np.random.permutation(npixels) # ノイズが重畳される画素のインデックス salt_idx = random_indices[:int(npixels*p/2.0)] pepper_idx = random_indices[int(npixels*p/2.0):int(npixels*p)] # 画像をフラットにした配列を取得 img_flat = img.flatten() img_flat[salt_idx] = 255 img_flat[pepper_idx] = 0 return img_flat.reshape(img.shape) 0 50 100 150 200 250 0 50 100 150 200 250 src 0 50 100 150 200 250 0 50 100 150 200 250 noise (p=0.15) 50/64
  37. メディアンフィルタの実行結果 img_median = cv2.medianBlur(img, 5) 0 50 100 150 200

    250 0 50 100 150 200 250 src 0 50 100 150 200 250 0 50 100 150 200 250 noise (p=0.15) 0 50 100 150 200 250 0 50 100 150 200 250 dst (median) 0 50 100 150 200 250 0 50 100 150 200 250 dst (blur) 51/64
  38. モルフォロジー変換の基本演算はメディアンフィルタに似てる • 膨張:メディアンフィルタの median を max に置き換えたもの • 収縮:メディアンフィルタの median

    を min に置き換えたもの • 一般のグレースケール画像についても定義されるが,2 値画像に 対して適用する例が多い 53/64
  39. モルフォロジー変換の構造化要素 (Structuring Element) • モルフォロジー変換のカーネルの形状を定義するもの • 矩形の範囲だけでなく,円形や棒状の範囲でのモルフォロジー 変換を作ることができる >>> cv.getStructuringElement(cv.MORPH_RECT,

    (5, 5)) array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]], dtype=uint8) >>> cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5)) array([[0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0]], dtype=uint8) 55/64
  40. マッチング:2 つの画像が同じかどうかを判断する問題 • 問題の例 • 回路基板の画像から特定の素子を探したい • ハンドジェスチャの種類を判定 • 2

    つのアプローチが紹介されている • 画素や勾配方向のヒストグラムのマッチング • 画像の類似度によるマッチング • デモ: https://docs.opencv.org/3.4/d8/dd1/tutorial_js_template_matching.html 56/64
  41. 画素値のヒストグラムを計算する 0 100 200 0 50 100 150 200 250

    src 0 100 200 Brightness 0 500 1000 1500 2000 2500 3000 Frequency histograms R channel G channel B channel • R–G 間の Bhattacharyya 距離: 0.630 • R–B 間の Bhattacharyya 距離: 0.428 • G–B 間の Bhattacharyya 距離: 0.574 57/64
  42. 画像のヒストグラムは OpenCV の機能でも Numpy の機能でも計 算できる 0 50 100 150

    200 250 0 2000 R channel 0 50 100 150 200 250 0 1000 2000 G channel 0 50 100 150 200 250 0 1000 2000 B channel 58/64
  43. 画像 I(x, y) とテンプレート画像 T(x, y) 間の類似度を測る方法 • 正規化二乗差分マッチング手法 (cv2.TM_SQDIFF_NORMED)

    • 完全一致は 0.不一致は大きい値になる. R(x, y) = ∑ x′,y′ [T(x′, y′) − I(x + x′, y + y′)]2 √∑ x′,y′ T(x′, y′)2 · ∑ x′,y′ I(x + x′, y + y′)2 • 正規化相互相関マッチング手法 (cv2.TM_CCORR_NORMED) • 一致すると大きな値,不一致は小さな値になり,最小値は 0 R(x, y) = ∑ x′,y′ T(x′, y′) · I(x + x′, y + y′) √∑ x′,y′ T(x′, y′)2 · ∑ x′,y′ I(x + x′, y + y′)2 • 正規化が無いバージョンもある.正規化したほうが明度変化に 強くなる. 60/64
  44. テンプレートマッチングの実行例 0 50 100 150 200 250 0 50 100

    150 200 250 src 0 10 20 30 40 50 60 70 80 0 20 40 60 80 template 0 25 50 75 100 125 150 0 20 40 60 80 100 120 140 distance map 0.1 0.2 0.3 0.4 0 50 100 150 200 250 0 50 100 150 200 250 result 61/64
  45. 演習:マウスの動画解析 https://bit.ly/2GiMNPA 今まで学習した内容でどこまでできそう? 1. マウスの体の中心位置を追跡 • 簡単そう 2. マウスの体の向きを追跡 •

    尻尾の位置がわかれば体の中心位置との組み合わせでいけそう 3. マウスの頭の向きを追跡 • 頭は体の向きと独立に動くので難しそう 64/64