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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for eqs eqs
April 28, 2021
120

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

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

Avatar for eqs

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