- 「OpenCVのあの機能、どうやって使うんだっけ?」
- 「OpenCVの基本的な部分が、いまいちよく分からない・・」
本記事では、
- OpenCVの基本部分をおさえた後に、
- 機能別の使い方
を紹介します。
目次をみて、必要な箇所だけ見て頂ければと思います。
「OpenCVが初めて」という方は、下記記事が参考になります。
インストール方法から、OpenCVを使った初めてのコードまで紹介しています。
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
みたいに、最近見えてます。
簡単な処理は、「執事」だけで対応可能。
複雑な処理は、「主人」が出てくるけど、学習やら、電気代とか色々面倒くさい・・
みたいな感じです。
ディープラーニングの仕組みに関しては、下記記事にてご紹介しています。
最後までお付き合い頂き、ありがとうございました。
また、お会いしましょう!