コンテンツにスキップ

第12回:画像処理

前回は matplotlib という外部ライブラリで、集計結果をグラフにした。今回も外部ライブラリを使う。テーマは画像処理、道具は OpenCV。画像をプログラムで読み込んで、サイズを変えたり、白黒にしたり、輪郭を抜き出したりする。

写真を 1 枚ずつ加工するのは無理

スマホで撮った写真 100 枚を、全部同じサイズに縮小したい。1 枚ずつペイントで開いて…は現実的でない。プログラムなら一瞬で大量の画像をまとめて処理できる

  • 例:SNS のアップロード制限に合わせてリサイズ、プロフィール写真をモノクロ加工、証明写真のサイズ調整

身近なものの内部でも画像処理は使われている。標準写真アプリ、Instagram や LINE のフィルター、スマホの顔認識、QR コードリーダ、レジのバーコード読み取り……。卒研の実験画像の処理や作業の自動化にも効く。画像をプログラムで扱えると強い

OpenCV と pip

画像処理には OpenCV(オープンシーヴィー) という外部ライブラリを使う。標準では入っていないので pip で入れる。

コマンドプロンプトで次を実行する。

> pip install opencv-python

※実行後はSpyderを再起動すること。

プログラムの先頭で次のように書くと、以降 cv2.〇〇 で機能を呼び出せる。

import cv2

ライブラリ名と読み込む名前が違う

インストールするパッケージ名は opencv-python だが、プログラムで読み込む名前は cv2

画像の読み込みと表示

  • cv2.imread(ファイル名) で画像を読み込む(変数に入る)
  • cv2.imshow(ウィンドウ名, 画像) で別ウィンドウに表示する
  • cv2.waitKey(0) でキー入力待ちにする(これが無いと一瞬で閉じてしまう)
  • cv2.destroyAllWindows() でウィンドウを閉じる
import cv2
img = cv2.imread('block.jpg')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

この 4 行セットが画像表示の基本形。画像ファイル(ここでは block.jpg)はプログラムと同じフォルダに置く。

手元の画像で試すには

block.jpg は説明用の見本画像。手元で動かすときは、自分の好きな .jpg ファイルを同じフォルダに置いてファイル名を書き換えれば OK(このあとの授業内演習では barbara.jpg を使う)。

画像のサイズを変える

  • 画像のサイズは img.shape で分かる
  • shape[0]高さshape[1](順番に注意)
  • cv2.resize(画像, (幅, 高さ)) でサイズを変える
  • cv2.imwrite(ファイル名, 画像) でファイルに保存する
img = cv2.imread('block.jpg')
height = img.shape[0]   # 高さ
width  = img.shape[1]   # 幅
resized = cv2.resize(img, (width // 2, height))   # 幅だけ半分にする
cv2.imwrite('resized.jpg', resized)

順番が逆:取り違え注意

img.shape(高さ, 幅) の順、cv2.resize の引数は (幅, 高さ) の順。なので取り違えやすい。

色を変える(グレースケール)

cv2.cvtColor(画像, cv2.COLOR_BGR2GRAY) で白黒(グレースケール)にできる。

OpenCV の色の順番は BGR(青・緑・赤)。RGB ではないので、定数名は BGR2GRAY

img = cv2.imread('block.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite('gray.jpg', gray)

グレー画像は色の情報が 1 つになるので、shape2 次元(高さ, 幅)になる(カラーは (高さ, 幅, 3) の 3 次元)。

エッジ検出(Canny)

cv2.Canny(画像, 閾値1, 閾値2) で、輪郭(エッジ)だけを抜き出した白黒画像が作れる。

閾値1・閾値2 を変えると検出する輪郭の量が変わる(小さくすると細かい線まで拾う)。

img = cv2.imread('block.jpg')
edge = cv2.Canny(img, 50, 100)
cv2.imwrite('edge.jpg', edge)

数字を調整して、輪郭がちょうどよく出る値を探すのがコツ(応用課題で使う)。

もっと知りたい人へ(発展):円の検出

エッジ(輪郭)が分かると、その中から特定の形を探せるようになる。cv2.HoughCircles(...) を使うと、画像の中の(道路標識・コイン・お皿など)を見つけられる。見つけた円は cv2.circle(...) で画像に上描きして確認する。

import cv2
import sys

img = cv2.imread('road_sign.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
canny_img = cv2.Canny(gray_img, 600, 650)

circles = cv2.HoughCircles(canny_img, cv2.HOUGH_GRADIENT, dp=1, minDist=10,
                           param1=700, param2=27, minRadius=20, maxRadius=60)
if circles is None:
    sys.exit()

for (x, y, r) in circles[0]:
    cv2.circle(img, (int(x), int(y)), int(r), (0, 255, 0), 6)

cv2.imshow('detected circles', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • import sys は、円が 1 つも見つからないとき sys.exit() で処理を止めるため
  • param1param2 などの数字を調整して、円がうまく検出されるよう工夫する
  • 見つかった円を for で 1 つずつ取り出し、cv2.circle で上描きする

授業内演習

barbara.jpg を読み込んで、画像を表示するプログラムを作成しなさい。

barbara.jpg は上のリンクからダウンロードし、プログラムと同じフォルダに置くこと。

確認の観点:

  • 本文「画像の読み込みと表示」の4 行セットを、barbara.jpg に当てはめる
  • cv2.waitKey(0) を入れないと、ウィンドウが一瞬で閉じてしまう

基本課題

barbara.jpg を読み込んで、キーボードから画像の縦サイズ横サイズを入力し、cv2.resize 関数を用いてサイズを変更して画像を表示するプログラムを作成しなさい。

実行例:

縦サイズを入力してください>> 200
横サイズを入力してください>> 150
→ 200×150 にリサイズされた画像が表示される

ヒント:

  • input() で受け取った値は文字列なので、int() で数値にする
  • cv2.resize の引数は (横, 縦) の順(本文「順番が逆」の注意を見直す)

応用課題

barbara.jpg を読み込んで、cv2.Canny 関数によってエッジ検出を行い、エッジ画像を表示して保存するプログラムを作成しなさい。

約束ごと:

  • エッジが鮮明に見えるように、cv2.Canny 関数の第2引数と第3引数(閾値1・閾値2)を調整して工夫すること
  • 保存したエッジ画像もアップロードすること