【画像処理入門】実務で使えるOpenCVの基礎

初心者の方へ

初心者の方はまず一番下の「notebookの配布」からnotebookをダウンロードして、Google Colaboratory上で実際に動かしながら学ぶことをおすすめします。

記事の内容はすべてnotebook上にも書かれていて、Google ColaboratoryはGoogleアカウントさえあれば無料で利用できます。


OpenCVを使った画像処理の基礎

ここでは画像処理の初歩となるものを紹介します。
単純な処理ばかりですが、これらの処理を組み合わせることで複雑な処理も可能です。
これから画像処理を習得しようという方はぜひ基本としておさえておくことをおすすめします。

画像を行列としてとらえるのがポイントです!

以下の例では画像データセットのCOCOの2017 Val imagesを使っています。

画像であればなんでもいいので、自分で撮った写真や仕事で使う予定の画像などを使っていただいても大丈夫です。
その場合はファイルの読み込みの箇所を変えるといったように適宜修正してお使いください。

必要なパッケージのインストール

今回使用するOpenCVはColabにデフォルトでインストールされているので、特に追加でインストールするパッケージはありません。

COCOのダウンロード

ここでは最新の2017版の中で一番ファイルが軽いValをダウンロードしています。Val2017に入っている画像の枚数は5000枚です。

!wget http://images.cocodataset.org/zips/val2017.zip

ダウンロードしたファイルの解凍

!unzip val2017.zip

ファイル名一覧の取得

画像処理そのものとは別の話になりますが、大量の画像を順次処理するような場合は、最初にファイル名一覧を取得しておくのがおすすめです。

自分の画像を1枚だけアップロードしてお試しになるような場合はこのステップは飛ばしていただいて大丈夫です。

import glob

# val2017の中にある〇〇.jpgというファイルを取得
files = glob.glob('val2017/*.jpg')

# 画像枚数を確認
print(len(files))

画像の読み込みと表示

おそらく一番使うことになる画像の読み込み処理です。

注意することとしては、カラー画像のときの色の順番がOpenCVではBGR(青、緑、赤)になっていることです。

RGB(赤、緑、青)で処理することのほうが多いと思うので、ここではその変換処理もあわせて紹介します。

import cv2

f_img = 'val2017/000000364587.jpg'
# 自分でアップロードした1枚の画像を使う場合は、ファイル名を指定してください
# f_img = 'ファイル名を自分で入力'

# 画像ファイルの読み込み
img = cv2.imread(f_img)

# GBRからRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

小技ですが".shape"をつけると、画像を行列として見たときの形がわかります。

この例だと縦422、横640、チャネルが3つなのでカラー画像、といった感じです。

print(img.shape)

(422, 640, 3)

出力例
from matplotlib import pyplot as plt

# 画像の表示
plt.imshow(img)
plt.show()

plt.imshow(img_rgb)
plt.show()
BGRの画像例
RGBの画像例

matplotlibを使った表示はRGBなので、GBRのままだと色がおかしくなっているのがわかると思います。

また、画像を行列として考えることで、以下のようにすればRGBの各チャネルだけにしたものを取り出すことができます。

plt.imshow(img_rgb[:,:,0])
plt.show()

plt.imshow(img_rgb[:,:,1])
plt.show()

plt.imshow(img_rgb[:,:,2])
plt.show()
R
G
B

画像のリサイズ

cv2.resize()を使うことで画像の大きさを変えることができます。

大きさを指定するときの縦横の順が.shapeで表示される値と逆になっているので注意しましょう。 cv2.resize()は横、縦の順で指定します。

また、小さくするときに元のN分の1とするときは"/"の割り算ではなく、商を求める"//"を使うのがおすすめです。

img_small = cv2.resize(img_rgb, (img_rgb.shape[1]//2, img_rgb.shape[0]//2))
plt.imshow(img_small)
plt.show()

img_large = cv2.resize(img_rgb, (img_rgb.shape[1]*2, img_rgb.shape[0]*2))
plt.imshow(img_large)
plt.show()
縮小例
拡大例

画像の上下反転、左右反転、90度回転

画像の簡単な変形処理です。

# 上下反転
img_fliptb = cv2.flip(img_rgb, 0)
plt.imshow(img_fliptb)
plt.show()

# 左右反転
img_fliplr = cv2.flip(img_rgb, 1)
plt.imshow(img_fliplr)
plt.show()

# 上下左右反転
img_fliptblr = cv2.flip(img_rgb, -1)
plt.imshow(img_fliptblr)
plt.show()
上下反転
左右反転
上下左右反転
# 時計まわりに90度回転
img_rot90 = cv2.rotate(img_rgb, cv2.ROTATE_90_CLOCKWISE)
plt.imshow(img_rot90)
plt.show()

# 時計回りに270度回転(反時計回りに90度回転)
img_rot270 = cv2.rotate(img_rgb, cv2.ROTATE_90_COUNTERCLOCKWISE)
plt.imshow(img_rot270)
plt.show()
90度回転
-90度回転

画像の切り抜き

これは行列として考えれば簡単です。
抜き出したい部分を値で指定しましょう。
以下の例では中央の車両を抜き出してみます。

pythonでの行列になじみのない人向けに少し説明すると、

  • 値の順番は縦、横、チャネルの順

  • 抜き出しせずそのままの箇所は":"を使用

  • 100~200を抜き出したいときは"100:200"のように指定

といったことを知っていれば問題ないと思います。

# 画像の抜き出し
img_extract = img_rgb[100:350, 150:450, :]
plt.imshow(img_extract)
plt.show()
抜き出し例

画像の二値化

目的とするものだけを抜き出したいときに、

  • 抜き出したいもの: 1

  • それ以外: 0

といった操作(二値化)はよく行われます。

今回はカラー画像なので、まずグレースケールに変換し、その後に二値化の処理をします。

ここではRGBからグレースケールに変換していますが、"cv2.COLOR_RGB2GRAY"を"cv2.COLOR_BGR2GRAY"とすれば、読み込んでそのままグレースケールに変換することもできます。

さらに、最初からカラーではなくグレースケールで読み込むこともできます。
そういったときは読み込むときに"cv2.imread('ファイル名', cv2.IMREAD_GRAYSCALE)"とします。

# 二値化に用いる閾値の設定
threshold = 150

# グレースケールに変換
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)

# 閾値以上を255、それ以外を0とする二値化の実行
_, img_binary = cv2.threshold(img_gray, threshold, 255, cv2.THRESH_BINARY)
# 1つ目に閾値の設定情報(この例では150)が返されますが、不要なので"_"としています

# cmap='gray'としてグレースケール(今回の場合はモノクロ)で表示
plt.imshow(img_binary, cmap='gray')
plt.show()
二値化の例

グレースケールにすると、チャネルが統合されるので行列の次元が1つ減り、縦、横の情報のみになっています。

img_binary.shape

(422, 640)

出力例

ここでは何も考えずに閾値を150としましたが、いざ自分でなにか二値化をすることになったときは、まずヒストグラムを描くのがおすすめです。

# グレースケール画像のヒストグラム
plt.hist(img_gray)
plt.show()
ヒストグラムの例

画像の値がどれくらいの領域に分布していて、分布に切れ目があるのかどうかが一目瞭然になります。この情報を元に閾値にあたりをつけるのがおすすめです。

画像の保存

画像処理を行った後は必ずと言っていいほど画像を保存することになると思います。

ここでは例として、車両を抜き出したものを保存しています。

また、大量に画像を保存したときのことを考えて、保存用ディレクトリをつくってそこに保存していますが、枚数が多くない場合は省略しても大丈夫です。

import os

# 保存用ディレクトリの名前を指定
dir_save = 'results'

# 保存用ディレクトリを作成
os.mkdir(dir_save)
# 保存するときはRGBからBGRに戻す
img_save = cv2.cvtColor(img_extract, cv2.COLOR_RGB2BGR)

# 画像を保存
cv2.imwrite(f'{dir_save}/train.jpg', img_save)

保存ディレクトリの圧縮
大量に画像を保存した場合、Colabから1枚ずつ画像をダウンロードするのは面倒です。

そういったときは、保存先ディレクトリを圧縮してzipファイルをダウンロードするのがおすすめです。

!zip -r results.zip results

指定した色の部分の抜き出し

入門から少し進んだ内容になりますが、特定の色の部分を抜き出す方法を最後に紹介します。

以下の例では画像中の黄色い部分(バナナ)を抜き出しています。

f_img = 'val2017/000000429281.jpg'

# 画像ファイルの読み込み
img = cv2.imread(f_img)

# GBRからRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()

特定の色を抜き出す場合、RGBだと指定が難しいのでHSVに変換します。

HSVは、色相(H)、彩度(S)、明度(V)、の3つのことで、色相だけでもおおまかな色を指定できるようになっています。

import numpy as np

# HSVへの変換
img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV)

# 各値の最小値、最大値を確認
print(f'H: min: {np.min(img_hsv[:,:,0])}, max: {np.max(img_hsv[:,:,0])}')
print(f'S: min: {np.min(img_hsv[:,:,1])}, max: {np.max(img_hsv[:,:,1])}')
print(f'V: min: {np.min(img_hsv[:,:,2])}, max: {np.max(img_hsv[:,:,2])}')

H: min: 0, max: 179
S: min: 0, max: 255
V: min: 0, max: 255

出力例
色相(H)と対応する値

色相の180段階はだいたい上図のように分かれています。

最初は少し広めの領域を取って、そこから徐々に調整していくのがおすすめです。

以下の例は最終的なものだけを示していますが、いきなりこの条件を見つけたわけではありません。

具体的には以下のような手順で試行錯誤を繰り返した結果です。

  1. 色相の図から20 < H < 40くらいとあたりをつける

  2. mask[(h > 20) & (h < 40)] = 255 から始めて、数字をせばめていく

  3. Sの条件を追加し、同じように範囲を狭めていく

  4. Vの条件を追加し、同じように範囲を狭めていく

また、各条件間の関係は、

  • &: ~かつ・・・

  • |: ~または・・・

となっています。

基本的には&でつなげばよいのですが、赤色のときだけは例外です。

上図を見るとわかるように、赤色は両端になっているので、そのときだけはHの条件を"((h < 15) | (h > 160))"のようにする必要があります。(OR条件なので全体を()でくくるのを忘れずに!)

# HSVそれぞれを抜き出す
h = img_hsv[:, :, 0]
s = img_hsv[:, :, 1]
v = img_hsv[:, :, 2]

# 検出結果を入れる変数を用意
mask = np.zeros(h.shape, dtype=np.uint8)

# 黄色部分の抜き出し
mask[(h > 18) & (h < 30) & (s > 150) & (v > 150)] = 255

plt.imshow(mask)
plt.show()
抜き出し例

つくったマスクを使って元画像から該当部分を抜き出すときは以下のようにします。

np.where()はnp.where(条件, 条件を満たした場合の値の指定, それ以外の値の指定)のように使います。

画像処理でも使える便利な関数なので覚えておくと便利です。 (例えば、二値化もnp.where()で可能です

# RGBの各層でマスクの何もない部分を白にする
# +を-にすると何も内部部が黒になる
r = img_rgb[:,:,0] + np.where(mask==0, 255, 0)
g = img_rgb[:,:,1] + np.where(mask==0, 255, 0)
b = img_rgb[:,:,2] + np.where(mask==0, 255, 0)

# 255を超えた部分を255にする
r = np.where(r>255, 255, r)
g = np.where(g>255, 255, g)
b = np.where(b>255, 255, b)

# RGBを統合
img_banana = np.stack([r, g, b], axis=2)

plt.imshow(img_banana)
plt.show()
抜き出し例

notebookの配布

以上のコードがすでに記入されたnotebook(.ipynb)を置いておきます。
Google Colabにアップロードしてすぐ使えるので、よろしければご利用ください。

配布notebookの使い方についてはこちら

他にもこういった記事が読みたい!これを解説してほしい!などご意見がありましたら、アンケートフォームからご意見をいただけるとうれしいです。



記事購入については、有料の追加部分はなく投げ銭用となっております。
役に立った!有用だった!という方はご検討よろしくお願いいたします。

ここから先は

0字

¥ 500

この記事が気に入ったらサポートをしてみませんか?