Pythonスキルの習得

【 Pythonで使うOpenCV 】 よく使用する機能まとめ

【 Pythonで使うOpenCV 】 よく使用する機能まとめ
  • 「OpenCVのあの機能、どうやって使うんだっけ?」
  • 「OpenCVの基本的な部分が、いまいちよく分からない・・」

本記事では、

  • OpenCVの基本部分をおさえた後に、
  • 機能別の使い方

を紹介します。

目次をみて、必要な箇所だけ見て頂ければと思います。

「OpenCVが初めて」という方は、下記記事が参考になります。

インストール方法から、OpenCVを使った初めてのコードまで紹介しています。

Pythonで画像処理を初めよう! OpenCV 入門
Pythonで画像処理を初めよう! OpenCV 入門Pythonによる、OpenCVの使い方をご紹介しました。インストール方法から、GUI上での画像表示方法まで、解説しています。...

OpenCVの基本部分

OpenCV内部で、画像をどのような形式で処理しているかを紹介します。

この部分が理解できれば、他のライブラリとのコラボ等、だいぶ応用ができるようになります。

また機械学習をされる方は、必須の知識になると思います。

例えば「ディープラーニング」の場合、入力データを「テンソル」に変換します。

OpenCVのデータ形式が理解できれば、そこで、つまずく事もなくなると思います。

OpenCVの画像データに関して

結論から言うと、

  • Numpyの配列である、ndarray形式
  • BGRの順番での配列(RGBの順番ではない。)

になります。

具体的に見ていきます。

話を簡単にするために、8pix(ピクセル)×8pixを読み込んでみます。

読み込む画像は以下のものになります。

筆者が人工的に作った画像になります。

この画像をOpenCVから読み込み、データ形式を確認してみます。

import cv2

# 画像ファイルの読込み
img = cv2.imread('8pximg.png') # 8px画像ファイルの読込み

# 型の確認
print(type(img))
#  ⬆結果  <class 'numpy.ndarray'>

# 配列 各要素の型を確認
print(img.dtype)
#  ⬆結果  uint8

# 配列のサイズを確認
print(img.shape)
#  ⬆結果  (8, 8, 3)

# 画像、一列目の配列を確認
print(img[0])
#  ⬆結果  すぐ下の記事で解説

つまり、

  • 画像の配列は、「Numpyのndarray」の型であり、
  • 配列に入っている各要素は、「uint8」(0〜255の整数)、
  • 配列サイズは、8×8×3

である事が分かります。

また上記スクリプトの最終行の結果と意味を解説します。

・ 結果の確認 – print(img[0]) の結果は以下になります-

[[ 33  32 153]
 [ 85  64 149]
 [ 73  66 118]
 [  4  19  46]
 [  0   0   0]
 [  0   3   0]
 [  0   5   4]
 [  0   5  18]]

この配列の意味は、下記の通りになります。

繰返しになりますが、OpenCVは各ピクセルを、BGRの順番で配列として表現しています。

機能別 使い方のご紹介

以下の処理は、 「import cv2」 が既に実施されている前提で、コードを記載しています。

読込み処理と画面表示(ファイル読込み、カメラからの入力処理)

入力して、ウインドウに表示するまでのコードをご紹介します。

画像ファイルの読込み → 画面表示

img = cv2.imread('画像ファイルへのパス')
# 例 img = cv2.imread('./img/test.jpg')

# GUIに表示
cv2.imshow('img', img)
# GUI上で何かキーをおすと、ウインドウが消える
cv2.waitKey(0)
cv2.destroyAllWindows()

画像ファイルをグレースケースで読込む場合

画像読込みのコードを、以下に変更して下さい。

img = cv2.imread('画像ファイルへのパス', 0)
# 例  img = cv2.imread('./img/test.jpg', 0)

動画ファイルの読込み→ 画面表示

cap = cv2.VideoCapture('動画ファイルへのパス')

# 動画終了まで、1フレームずつ読み込む
while(cap.isOpened()):
    # 1フレーム毎 読込み
    ret, frame = cap.read()
    # ret: 画像情報が含まれているか True or False
    # frame 画像情報そのもの

    # GUIに表示
    cv2.imshow("Movie", frame)
    # qキーが押されたら途中終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 終了処理
cap.release()
cv2.destroyAllWindows()

カメラからの入力→ 画面表示

cap = cv2.VideoCapture(0)

while(True):
    # 1フレーム毎 読込み
    ret, frame = cap.read()
    
    # GUIに表示
    cv2.imshow('frame',frame)
    # qキーが押されたら終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 終了処理
cap.release()
cv2.destroyAllWindows()

上記コードのVideoCapture( ) の引数の0 は、device_idを入力します。

PCにカメラを接続した際、OSにが自動で割り振ります。

1台目のdevice_idは、0 から割り振られます。(内蔵カメラがあるPCは、内蔵カメラに 0 が割り振られます。)

保存処理(画像、動画ファイルへ)

画像をファイルに保存する

# img = cv2.imread('test_img.jpg') 等で読込んでいたとする

cv2.imwrite('保存ファイルへのパス', img)

ファイルの拡張子を.pngにすると、PNGファイル形式で保存されます。

JPG保存の際、品質レベルを設定する

0〜100(最高品質)で指定し、95がデフォルト(設定しない場合)になります。

下記コードの最後の50が品質レベルに該当します。

cv2.imwrite('保存ファイルへのパス', img, [cv2.IMWRITE_JPEG_QUALITY, 50])

動画ファイルに保存する

動画ファイルの読込み、終了処理が省略したコードになります。

1フレーム毎、保存していく形を取ります。

# 動画ファイル保存のため、必要情報の設定
# フレームレート
frame_rate = 24.0 
# 動画の画面サイズ
size = (640, 480)
# 動画のファル形式を指定 (下記の場合、MP4)
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')

# 動画ライターの作成
writer = cv2.VideoWriter('保存ファイルへのパス', fmt, frame_rate, size)

while(cap.isOpened()):
    # 1フレーム毎 読込み
    ret, frame = cap.read()

    # 保存処理
    writer.write(frame)

    # GUIに表示
    cv2.imshow("Movie", frame)
    # qキーが押されたら途中終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

OpenCV ↔ Pillow 変換

カラー画像の場合、チャンネルの順番が異なるため、注意が必要です。

  • OpenCV → BGR(A)の順番
  • Pillow → RGB(A)の順番

OpenCV → Pillow 変換

import cv2
from PIL import Image

opencv_img = cv2.imread('画像ファイルへのパス')

if opencv_img.shape[2] == 3:  # カラー画像
    opencv_image = cv2.cvtColor(opencv_img, cv2.COLOR_BGR2RGB)
elif opencv_img.shape[2] == 4:  # 透過カラー画像
    opencv_image = cv2.cvtColor(opencv_img, cv2.COLOR_BGRA2RGBA)

pillow_img = Image.fromarray(opencv_img)

Pillow → OpenCV 変換

import numpy as np
from PIL import Image

pillow_img = Image.open('画像ファイルへのパス')

opencv_image = np.array(pillow_img, dtype=np.uint8)

# カラー画像の場合
if opencv_image.shape[2] == 3:  
    opencv_image = cv2.cvtColor(opencv_image, cv2.COLOR_RGB2BGR)
# 透過カラー画像の場合
elif opencv_image.shape[2] == 4: 
    opencv_image = cv2.cvtColor(opencv_image, cv2.COLOR_RGBA2BGRA)

読み込んだ画像、動画のプロパティ(画像サイズ等)の取得

画像サイズ、チェンネル数の取得

img = cv2.imread('画像ファイルへのパス')

# 読み込んだファイルは、numpy.ndarray型
height, width, channel = img.shape
print('横サイズ:  ', width)
print('縦サイズ: ', height)
print('チャンネル:', channel)

動画サイズ、FPS、総フレーム数、総再生時間を取得

# 動画の読み込み
cap = cv2.VideoCapture(0) # カメラからの場合
# cap = cv2.VideoCapture("動画ファイルへのパス") # ファイルの場合

# 横サイズ
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
# 縦サイズ
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# FPS
fps = cap.get(cv2.CAP_PROP_FPS)
# 総フレーム数
frame_num = cap.get(cv2.CAP_PROP_FRAME_COUNT)
# 総再生時間
play_time = frame_num / fps

画像に図形を描写

比較的、よく使用するものをご紹介します。

長方形を描く

img = cv2.imread('画像ファイルへのパス')

cv2.rectangle(img, (50, 50), (100, 100), (0, 0, 255), thickness=1, lineType=cv2.LINE_8, shift=0)
# GUIに表示
cv2.imshow('img', img)

上記コードのcv2.rectangle関数で、デフォルト値がある引数を、デフォルト引数で表現しました。(省略すると、コード中に記載した値が使用されます。)

引数の説明:

  • 第2引数 → 長方形の左上の座標 (px)
  • 第3引数 → 長方形の右下の座標 (px)
  • 第3引数 → BGRの順番で色の指定(ここでは、赤を指定)
  • thickness → 線の厚さ (px)、 -1にすると、長方形内全てが、塗りつぶされる。
  • lineType → 線の種類 (CV2.LINE_4:4連結線、CV2.LINE_8: 8連結線、cv2.LINE_AA:アンチエイリアス線)
  • shift → 正の値の場合、座標を 1 / shift 倍にスケールする

◇ 実行結果

元の画像に上書きされる形で、書き込まれます。

線を描く

img = cv2.imread('画像ファイルへのパス')

cv2.line(img, (0, 0), (250, 250), (0, 255, 0), thickness=1, lineType=cv2.LINE_8, shift=0)
# GUIに表示
cv2.imshow('img', img)

上記コードのcv2.line関数で、デフォルト値がある引数を、デフォルト引数で表現しました。(省略すると、コード中に記載した値が使用されます。)

引数の説明:

  • 第2引数 → 長方形の左上の座標 (px)
  • 第3引数 → 長方形の右下の座標 (px)
  • 第4引数 → BGRの順番で色の指定(ここでは、緑を指定)
  • thickness → 線の厚さ (px)、 -1にすると、長方形内全てが、塗りつぶされる。
  • lineType → 線の種類 (CV2.LINE_4:4連結線、CV2.LINE_8: 8連結線、cv2.LINE_AA:アンチエイリアス線)
  • shift → 正の値の場合、座標を 1 / shift 倍にスケールする

◇ 実行結果

元の画像に上書きされる形で、書き込まれます。

文字を描く

img = cv2.imread('画像ファイルへのパス')

cv2.putText(
    img,
    'test',
    (0, 200),
    cv2.FONT_HERSHEY_SIMPLEX,
    1.0,
    (0, 255, 0),
    thickness=1,
    lineType=cv2.LINE_8,
    bottomLeftOrigin=False
)
# GUIに表示
cv2.imshow('img', img)

上記コードのcv2.putText関数で、デフォルト値がある引数を、デフォルト引数で表現しました。(省略すると、コード中に記載した値が使用されます。)

引数の説明:

  • 第2引数 → 表示する文字列
  • 第3引数 → 描画開始位置(文字のbaselineの始点)
  • 第4引数 → フォントの種類 (公式ドキュメントはこちら)
  • 第5引数 → フォントの倍率
  • 第6引数 → BGRの順番で色の指定(ここでは、緑を指定)
  • thickness → 線の厚さ (px)
  • lineType → 線の種類 (CV2.LINE_4:4連結線、CV2.LINE_8: 8連結線、cv2.LINE_AA:アンチエイリアス線)
  • bottomLeftOrigin → Trueの場合、左下を原点として扱う。

◇ 実行結果

元の画像に上書きされる形で、書き込まれます。

画像処理

比較的、よく使用するものをご紹介します。

画像をリサイズ

img = cv2.imread('ファイルへのパス')

# リサイズ後の画像サイズを指定 (pix)
width = 100
height = 100

# リサイズ処理
img = cv2.resize(img,(width,height))

画像をグレースケール化

img = cv2.imread('ファイルへのパス')

# グレースケール化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

画像を2値化

下記で紹介する関数(cv2.threshold)は、グレー画像を入力する必要があります。

# グレースケールで読込み
img = cv2.imread('画像へのファイルパス', 0)

threshold = 100  # 閾値を設定
ret, img_th = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
# ⬆ img_th が 2値画像が になる。

2値化にあたり、以下の処理がされます。

  • 各画素で、値が閾値を超えたもの → MAX値 (上記関数の第3引数の255)
  • 各画素で、値が閾値を超えたもの → 0

画像の回転

ここでは、90°刻みで回転する場合をご紹介します。

img = cv2.imread('画像へのファイルパス')

img_rotate = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

cv2.rotate関数の第2引数は、

  • cv2.ROTATE_90_CLOCKWISE → 時計回りに90度
  • cv2.ROTATE_90_COUNTERCLOCKWISE → 反時計回りに90度
  • cv2.ROTATE_180 → 180度

になります。

上下、左右に反転

img = cv2.imread('画像へのファイルパス')

img_flip = cv2.flip(img, 0)

cv2.flip関数の第2引数は、

  • 0 → 上下反転
  • 0より大きい値(例えば 1) → 左右反転
  • 0より小さい値 (例えば -1)→ 上下左右反転

になります。

カラーの順番の変更( BGR ↔ BGR 変換 )

img = cv2.imread('画像へのファイルパス')

# BGR → RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# RGB → BGR
img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2RGB)

画像認識

比較的、よく使用するものをご紹介します。

画像のエッジ検出

「Sobel法」「 Laplacian法」「Canny法」と様々なものがありますが、「Canny法」を紹介します。

画像からノイズをうまく取り除き、検出する方法です。

実は内部で、「Sobel」を使っています。

# グレースケールで読み込む
img = cv2.imread('画像ファイルへのパス', 0)
# Canny法を適用
img_canny = cv2.Canny(img, 100, 200)
# GUI表示
cv2.imshow('canny', img_canny)

cv2.Canny の引数説明:

  • 第2引数 → 下の閾値 (それ以下を無視する。)
  • 第3引数 → 上の閾値 (それ以上を無視する。)

0〜255の範囲で、下の閾値と上の閾値を調整する事で、精度良くエッジ検出できる値を探します。

◇ 実行結果(変換前後)

画像内から、顔、目を検出

カスケード分類器を使った手法になります。

カスケード分類器とは?

検出したい物体の特徴(特徴量と呼ばれている)を使って、検出する方法です。

事前準備

下記コードでは、「顔、目」認識のための特徴分類器を使用しています。

これらは、外部ファイルとして読込む必要があります。(OpenCVをインストールしたら、一緒にインストールされます。)

顔検出 → haarcascade_frontalface_default.xml
目検出 → haarcascade_eye.xml

GitHubの公式からでも、DLできます。

img = cv2.imread('画像ファイルへのパス')

#カスケード型分類器を読み込み (ファイルパスを修正して下さい。)
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

# 顔を検出
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(img_gray, scaleFactor=1.1, minNeighbors=2, minSize=(100, 100))

# 複数の顔を検出する場合もあるため、for分で回している
for (x, y, w, h) in faces:
    # 検出した顔を線で囲む
    img = cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2)

    # 目を検出処理
    # 元画像から顔部分のみを抽出
    face_img = img[y:y+h, x:x+w]
    # 目を検出
    eyes = eye_cascade.detectMultiScale(face_img)
    # 検出した目を線で囲む
    for (ex, ey, ew, eh) in eyes:
        cv2.rectangle(face_img, (ex, ey), (ex + ew, ey + eh), (255, 0, 0), 2)

cv2.imshow('canny', img)

上記コードのface_cascade.detectMultiScale(あるいは、eye_cascade.detectMultiScale)関数の引数のご紹介です。

引数の説明:

  • scaleFactor → 画像スケールの縮小量。この方法は、画像スケールを何度も変化させて検索するので、その際の縮小量を設定する。大きいほど誤検知、小さいほど未検知になる。
  • minNeighbors → 検出信頼性のパラメータ。物体候補となる箇所で、設定した数以上の重複があると、検出される。つまり値が高いほど、信頼性は高くなるが、未検知になる可能性がある。
  • minSize → 物体が取り得る最小サイズ。これよりも小さい物体は無視されます。
  • maxSize → 物体が取り得る最大サイズ。(上記コード中では、表記しておりません。)

◇ 実行結果(変換前後)

最後に

本記事では、OpenCVの様々な機能をご紹介していきました。

他にもガンマ補正だったり、様々なものがありますが、あえて省略しました。(特定の領域に携わっている方以外、あまり使用しないと思ったからです。)

2021年現在、ディープラーニングが画像認識技術のメインとなりつつありますが、OpenCVはその前処理、後処理において、頻繁に使用されています。

私個人、ディープラーニングに携わっており、

  • 主人: ディープラーニング
  • 執事(あるいはメイド?): OpenCV

みたいに、最近見えてます。

簡単な処理は、「執事」だけで対応可能。

複雑な処理は、「主人」が出てくるけど、学習やら、電気代とか色々面倒くさい・・ 

みたいな感じです。

ディープラーニングの仕組みに関しては、下記記事にてご紹介しています。

ディープラーニングとは ? イメージでわかりやすく仕組みを解説
ディープラーニングとは ? イメージでわかりやすく仕組みを解説 世間で騒がれているけど、ディープラーニングって何? AI(人工知能)とどう違うの!? 筆者 今回は、「とある女性がプロ...

最後までお付き合い頂き、ありがとうございました。

また、お会いしましょう!