見出し画像

RaspberryPiで遊ぶ OpenCVによる画像処理

RaspberryPiで遊んでます。
つないだカメラで撮影した画像をどう扱うか。

Open CVは、オープンソースのライブラリ。
画像や動画を処理するのに必要な、様々な機能を提供してくれる。

Open CVでは、こんなことができます。
・画像のフィルタ処理
・特徴の抽出
・物体検知

カメラモジュールをつないだRaspberryPiと組み合わせれば、
目的に応じた監視カメラも作れる。
こんな方はぜひ。
・工作好き
・プログラミング好き
・監視カメラの仕組みを理解したい





画像処理

OpenCV-Pythonでは、Numpyの多次元配列を使用して、画像データを表現する。
カラー画像を「3次元配列」、白黒のグレースケール画像を「2次配列」で表現する。

画像の読み込み・書き込み

画像の読み込み

・image_read.py

# -*- coding: utf-8 -*-
'''
画像ファイル読み込み
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 第2引数  画像の読み込み方法を指定するためのコード値.
#
# cv2.IMREAD_COLOR : カラー画像として読み込む.画像の透明度は無視される.デフォルト値
# cv2.IMREAD_GRAYSCALE : グレースケール画像として読み込む
# cv2.IMREAD_UNCHANGED : アルファチャンネルも含めた画像として読み込む
# Note 上記のフラグを使う代わりに,単に1, 0, -1 の整数値を与えて指定することも可能

# ファイルから画像を読み込む
img = cv2.imread('../img/girl01.jpg',cv2.IMREAD_COLOR)

# 読み込んだ画像をキー入力があるまで画面表示する
# 画像をウィンドウ上に表示するには cv2.imshow() という関数を利用
# ウィンドウのサイズは自動で画像サイズ応じたサイズになる
cv2.imshow('image',img)
# cv2.waitKey() はキーボード入力を処理する関数 引数は入力待ち時間でミリ秒単位で指定
# この関数は,指定された時間だけキーボード入力を受け付ける
# 引数に 0 を指定した時は,何かしらのキーを打つまでキー入力を無期限で待ち続ける
cv2.waitKey(0)

#画面表示した画像を閉じる
# cv2.destroyAllWindows() は現在までに作られた全てのウィンドウを閉じる関数
# 特定のウィンドウのみを閉じる場合は cv2.destroyWindow() 関数に閉じたいウィンドウ名を指定
cv2.destroyAllWindows()

画像の書き込み

・image_write.py

# -*- coding: utf-8 -*-
'''
画像を扱う
Created on 2022/11/22
@author: shinmr
'''
import cv2

# ファイルから画像を読み込む
img = cv2.imread('../img/girl01.jpg',cv2.IMREAD_COLOR)

# 画像をウィンドウ上に表示
cv2.imshow('image',img)
cv2.waitKey(0)

cv2.destroyAllWindows()
# 画像をpng形式で保存 
cv2.imwrite('./dst/girl01_out.png',img)

画像の加工

画素の取得

画像の属性情報を取得し、様々な画像処理を実施することができる。
画像の属性情報
・列の数
・行の数
・チャンネル数(色相数)
・画像のデータ型
・画素数
など
ある画素の行と列の座標を指定することで画素値にアクセスできる。
RGB画像の画素値は赤、緑、青の色成分の値の配列、グレースケール画像の
画素値は明るさの値を返す。

画像の形状は、img.shapeによって取得する。
戻り値は行数、列数、チャンネル数(カラー画像の場合)のtuple。

合計画素数は、img.size で取得する。
画像の特定の領域に対して何らかの処理をする場合、注目領域(ROI:Region of Interest)の指定にはNumpyのインデックスを使う。

・image_analysis.py

# -*- coding: utf-8 -*-
'''
画像画素解析
Created on 2022/11/22
@author: shinmr
'''
import cv2

# カラー画像を読み込み
img = cv2.imread('../img/girl01.jpg')

# 画素値にアクセス
# RGB画像の画素値は赤,緑,青の色成分の値の配列
# グレースケール画像の画素値は明るさの値を返す
px = img[100100]
print('px :', px)

# x:100, y:100 の位置の青色,緑色,赤色 の濃度を調べる
# accessing only blue pixel

blue = img[1001000]
print('blue :', blue)

# accessing only green pixel
green = img[1001001]
print('green :', green)

# accessing only red pixel
red = img[1001002]
print('red :', red)

# 画像の属性情報の取得
# 画像の属性情報とは,
# 列の数,行の数,チャンネル数(色相数),画像データの型,画素数などを意味する
# 画像の形状は img.shape によって取得
# 戻値は 行数,列数,チャンネル数(カラー画像であれば)のtuple
print('img.shape :', img.shape)
# 合計画素数は img.size で調べる
print('img.size :', img.size)
# 画像データのデータ型は img.dtype によって取得
print('img.dtype :', img.dtype)

RGB分割

・image_RGB.py

画像はRGBの三原色で構成される。
色成分を抽出し、合成を行うことができる。

RGB、BGR、共に光の三原色をベースとした表現方法。
ライブラリによって、色の順番が異なるので注意。

https://neuro-ai.jp/?p=1096

RGB:Pillowでは色の順番はRGB(赤、緑、青)を前提としている。
BGR:OpenCVの関数imread()で画像ファイルを読み込むと色の順番がBGR(青、緑、赤)になる。

# -*- coding: utf-8 -*-
'''
画像をBGR、それぞれ成分を抽出し、色成分の合成を行う
合成する色成分の1色だけにデータがあり、ほかが0の場合は1色になる
Created on 2022/11/22
@author: shinmr

'''
import cv2
import numpy as np

# 画像ファイル読み込み
img = cv2.imread('../img/girl01.jpg',cv2.IMREAD_COLOR )

# 画像の縦、横サイズを取得
height, width = img.shape[:2]

# 画像の縦、横サイズを0に初期化した2次元配列を準備
zeros = np.zeros((height,width), img.dtype)

# BGRの配色で画像を分割
blue, green, red = cv2.split(img)

# 色成分を合成
blue = cv2.merge((blue, zeros, zeros))
green = cv2.merge((zeros, green, zeros))
red = cv2.merge((zeros, zeros, red))

# 合成語の画像をそれぞれ表示
cv2.imshow('image_blue',blue)
cv2.imshow('image_green',green)
cv2.imshow('image_red',red)

# キーボードから0を入力する事でウィンドウをクローズ、処理終了する
cv2.waitKey(0)
cv2.destroyAllWindows()

ぼかし

画像の平均をとり、ぼかし処理をするブラーという手法がある。
オブジェクト検出の精度を上げるために利用される。
境界が際立っていると、オブジェクト検出の精度が下がるため、意図的に境界をぼかす。

・image_blur.py

ぼかし画像を作成するサンプルコード。

# -*- coding: utf-8 -*-
'''
ぼかし処理
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 画像ファイルを読み込む
img = cv2.imread('../img/girl01.jpg',cv2.IMREAD_COLOR )

# 元画像を表示
cv2.imshow('girl01.jpg',img)

# 画像をぼかす(10,10) 縦、横にぼかす数値
blur = cv2.blur(img,(10,10))

# ぼかし画像を表示
cv2.imshow('girl01_blur',blur)

# キーボードから0を入力する事でウィンドウをクローズし、処理終了する
cv2.waitKey(0)
cv2.destroyAllWindows()

リサイズ

画像サイズを小さくしたり、大きくしたりといったリサイズができる。

モザイク

リサイズで画像を小さくすると画素情報が失われるので、元のサイズに戻すと画質が荒くなる。モザイク加工はこの仕組みと利用している。

・image_mozaic.py

モザイク加工するサンプルコード。

# -*- coding: utf-8 -*-
'''
画像のモザイク加工
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 画像ファイル読み込み
img = cv2.imread('../img/girl01.jpg',cv2.IMREAD_COLOR)

# 画像の横幅取得
width = img.shape[1]

# 画像の縦幅取得
height = img.shape[0]

# 画像の横、縦サイズを1/5にし、整数に丸め、タプルにセット
size = (int(width/5), int(height/5))

# 画像を指定サイズにリサイズ
img_resized = cv2.resize(img , size, interpolation=cv2.INTER_NEAREST)

# 画像を元のサイズにリサイズ
img_resize_orginalsize = cv2.resize(img_resized , (width, height), interpolation=cv2.INTER_NEAREST)

# リサイズ後の画像の表示
cv2.imshow('img_resize_orginalsize',img_resize_orginalsize)

# キーボードから0を入力する事でウィンドウをクローズし、処理終了する
cv2.waitKey(0)
cv2.destroyAllWindows()

二値化

二値化とは画像を白と黒の二色で表現すること。
二値化の前に、画像を明るさに応じてモノクロに変換するグレースケール化を行う。
グレースケール化では、最も暗い部分が「0」、最も明るい部分が「255」となる。
二値化を行う場合は、閾値に「127」を設定したりする。

・image_binary.py

# -*- coding: utf-8 -*-
'''
画像の二値化
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 画像ファイルを読み込む
img = cv2.imread('../img/girl01.jpg', cv2.IMREAD_COLOR)

# 画像をグレースケールに変換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)

# 画像の二値化 閾値:127(127を超える明るさを255に変換)
ret, th = cv2.threshold(gray, 127,255, cv2.THRESH_BINARY)
cv2.imshow('img1', th)

# キーボードから0を入力する事でウィンドウをクローズし、処理終了する
cv2.waitKey(0)
cv2.destroyAllWindows()

エッジ検出

画像のエッジを顕出する手法としてCannyフィルタという手法がある。
エッジとは明るさが急激に変化している部分を指し、エッジを検出することで物体の境界を得る手法。

・image_canny.py

# -*- coding: utf-8 -*-
'''
エッジ検出
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 画像ファイルを読み込む
img = cv2.imread('../img/girl01.jpg', cv2.IMREAD_GRAYSCALE)

# エッジ検出
frame = cv2.Canny(img,40,30)

# 画面に表示する
cv2.imshow('frame',frame)
cv2.waitKey(0)
cv2.destroyAllWindows()

オブジェクト検出

画像から下記を検出するサンプル。
・円
・顔
・目

円の検出

OpenCVのライブラリには、円を検出できるものがある。

円の検出にはハフ変換を利用する。
認識精度を上げるため、前処理としてグレースケール化、ぼかし処理を行う。

ハフ変換を実施する場合、認識精度を上げるために、パラメータ調整する必要がある。

ハフ変換でチューニングするパラメータは下記。

  • minDist:
    円が多く検出された場合、指定したピクセル値以下の円は検出対象から外す

  • param1:
    Cannyフィルタに渡すパラメータ。
    値が小さいと検出率が上がるが、誤検出も増える

  • param2:
    円の中心値に用いられる。
    値が小さいと検出率が上がるが、誤検出も増える

  • minRadius:
    指定した値よりも半径が小さい円は検出対象から外す

  • maxRadius:
    指定した値よりも半径が大きい円は検出対象から外す

・detect_circle.py

# -*- coding: utf-8 -*-
'''
円の検出
Created on 2022/11/22
@author: shinmr
'''
import cv2
import numpy as np

# 画像の読み込み
img = cv2.imread('../img/smile_stamp.png')

# グレースケール化
# img_med = cv2.medianBlur(img,5)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# ガウシアンぼかしを適用して、認識精度を上げる
blur = cv2.GaussianBlur(gray, (9,9), 0)

# ハフ変換を適用し、映像内の円を探す
circles = cv2.HoughCircles(blur, cv2.cv.CV_HOUGH_GRADIENT,
                      dp=1, minDist=50, param1=450, param2=240,
                      minRadius=5, maxRadius=500)

# 検出された円を描画
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
    # draw the outer circle
    cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2)
    # draw the center of the circle
    cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)

cv2.imshow('detected circles',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

ガウシアンぼかし

ガウス関数をもちいて画像をぼかす処理。デジタルカメラの撮像画像などからノイズを除去したり、アンシャープマスク処理、エッジ抽出の前処理などに使う。


https://www.weblio.jp/wkpja/content/%E3%82%AC%E3%82%A6%E3%82%B7%E3%82%A2%E3%83%B3%E3%81%BC%E3%81%8B%E3%81%97_%E3%82%AC%E3%82%A6%E3%82%B7%E3%82%A2%E3%83%B3%E3%81%BC%E3%81%8B%E3%81%97%E3%81%AE%E6%A6%82%E8%A6%81

ハフ変換

画像変換手法の一種。
画像の中から直線や円の要素を持つオブジェクトを検出する方法として良く知られている。
画像から直線や円を検出する為には、それらの形状情報を画像から抽出し、ハフ変換では画像空間からρ-θパラメータ空間への変換を行うことで、図形要素を抽出する。


http://opencv.jp/sample/special_transforms.html


Hough変換(直線の検出)の方法
https://codezine.jp/article/detail/153


Hough変換(円の検出)の方法
https://codezine.jp/article/detail/153

顔の検出

複雑な画像を識別するには、機械学習によりオブジェクトの特徴を学習したデータ(学習モデル)を利用する。
OpenCVでは、顔の特徴を学習したXMLファイル(カスケードファイル)を用いて実施する。
顔検出用のカスケードファイルをもとに学習モデル(カスケード分類器)により顔の検出を実施する。
事前にグレースケール化すると顔検出の精度が上がる。

・detect_face.py

顔を検出するサンプルコード。

# -*- coding: utf-8 -*-
'''
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 画像の読み込み
img = cv2.imread('../img/girl01.jpg')

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

# print(type(img), img.shape)
# print(type(img_gray), img_gray.shape)
# <class 'numpy.ndarray'> (512, 512, 3)
# <class 'numpy.ndarray'> (512, 512)

# haarcascade_smile.xml・・笑顔検出用のカスケードファイル
# haarcascade_eye.xml・・目検出用のカスケードファイル
# haarcascade_frontalface_alt.xml・・顔検出用のカスケードファイル
# cascade_path = '../haarcascades/haarcascade_smile.xml'
#cascade_path = '../haarcascades/haarcascade_eye.xml'
cascade_path = '../haarcascades/haarcascade_frontalface_alt.xml'

# 顔検出用のカスケード分類器作成
cascade = cv2.CascadeClassifier(cascade_path)

# 顔検出の実施
faces = cascade.detectMultiScale(img_gray)

#検出した顔部分を枠で囲む
for x, y, w, h in faces:
    cv2.rectangle(img, (x, y), (x + w, y + h), (25500), 2)

# 画面に表示
cv2.imshow('frame',img)

# キーボードから0を入力する事でウィンドウをクローズし、処理終了する
cv2.waitKey(0)
cv2.destroyAllWindows()

顔を自動でトリミング

「顔」が検出できれば、その部分を取り出してトリミングできる。

・triming_face.py

顔を自動でトリミングするサンプルコード。

# -*- coding: utf-8 -*-
'''
Created on 2022/11/22
@author: shinmr
'''
import cv2

#画像ファイルの読み込み
img = cv2.imread('../img/girl01.jpg')

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

# print(type(img), img.shape)
# print(type(img_gray), img_gray.shape)
# <class 'numpy.ndarray'> (512, 512, 3)
# <class 'numpy.ndarray'> (512, 512)

# 顔検出用のカスケード分類器作成
cascade_path = '../haarcascades/haarcascade_frontalface_alt.xml'
cascade = cv2.CascadeClassifier(cascade_path)

# 顔検出の実施
face = cascade.detectMultiScale(img_gray)
print("faces :", face)

#顔の座標を表示する
# print(face)
face_cut = None

#顔部分をトリミング
for x,y,w,h in face:
    face_cut = img[y:y+h, x:x+w]

# トリミングした画像表示
cv2.imshow('frame',face_cut)
cv2.waitKey(0)
cv2.destroyAllWindows()

目の検出

顔画像の目を検出するカスケードファイルを使って、目を検出する。

顔画像以外の部分の誤検出を防ぐため、
最初に顔検出を行い、次に検出した顔に対して目の検出をする。
精度とパフォーマンスが向上する。

・detect_eyes.py

# -*- coding: utf-8 -*-
'''
Created on 2022/11/22
@author: shinmr
'''
import cv2

# 顔検出用のカスケード分類器作成
cascade_path_face = '../haarcascades/haarcascade_frontalface_alt.xml'
face_cascade = cv2.CascadeClassifier(cascade_path_face)

# 目検出用のカスケード分類器作成
cascade_path_eye = '../haarcascades/haarcascade_eye.xml'
eye_cascade = cv2.CascadeClassifier(cascade_path_eye)

# 画像の読み込み
img = cv2.imread('../img/girl01.jpg')

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

faces = None
eyes = None

# 顔検出の実施
faces = face_cascade.detectMultiScale(img_gray, scaleFactor=1.1, minNeighbors=3)

if faces is None:
    print("face not detected")
else:
    # 枠で顔を囲む
    # for x1, y1, w, h in faces:
    #     cv2.rectangle(img, (x1, y1), (x1 + w, y1 + h), (0, 0, 255), 2)
    #顔部分をトリミング後、目の検出
    for x1,y1,w,h in faces:
        face_cut = img[y1:y1+h, x1:x1+w]
    # 目検出の実施
    eyes = eye_cascade.detectMultiScale(face_cut, scaleFactor=1.3, minNeighbors=5)
if eyes is None:
    print("eyes not detected")
else:
    #検出した目部分を枠で囲む
    for x2, y2, w, h in eyes:
        cv2.rectangle(img, (x1 + x2, y1 + y2), (x1 + x2 + w, y1 + y2 + h), (25500), 2)
    cv2.imshow('eyes',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

円の検出(リアルタイム)

リアルタイムでオブジェクトを検出する。

realtime_detect_circle.py

カメラから取り込んだ映像に対して
リアルタイムで円を検出するサンプルコード。

# -*- coding: utf-8 -*-
'''
Created on 2022/11/22
@author: shinmr
'''
import picamera
import picamera.array
import cv2

with picamera.PiCamera() as camera:
    with picamera.array.PiRGBArray(camera) as stream:
        camera.resolution = (320240)
        while True:
            # stream.arrayにBGRの順で映像データを格納
            camera.capture(stream, 'bgr', use_video_port=True)

            # 映像データをグレースケール化
            gray = cv2.cvtColor(stream.array, cv2.COLOR_BGR2GRAY)

            # ガウシアンぼかしを適用して、認識精度を上げる
            blur = cv2.GaussianBlur(gray, (9,9), 0)

            # ハフ変換を適用し、映像内の円を探す
            circles = cv2.HoughCircles(blur, cv2.cv.CV_HOUGH_GRADIENT,
                      dp=1, minDist=50, param1=120, param2=40, 
                      minRadius=5, maxRadius=100)
            if circles is not None:
                for c in circles[0]:
                    # 見つかった円の上に赤い円を元の映像(system.array)上に描画
                    # c[0]:x座標, c[1]:y座標, c[2]:半径
                    cv2.circle(stream.array, (c[0],c[1]), c[2], (0,0,255), 2)

            # system.arrayをウインドウに表示
            cv2.imshow('frame', stream.array)

            # "q"を入力でアプリケーション終了
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            # streamをリセット
            stream.seek(0)
            stream.truncate()
        cv2.destroyAllWindows()

顔の検出(リアルタイム)

・take_photo_realtime_detect_face.py

カメラから取り込んだ映像をリアルタイムで顔検出し、検出したタイミングでカメラ撮影するサンプルコード。

# -*- coding: utf-8 -*-
'''
Created on 2022/11/22
@author: shinmr
'''
import picamera
import picamera.array
import cv2
import pygame
import sys
from datetime import datetime

pygame.init()
size=(320,240)
screen = pygame.display.set_mode(size)

def pygame_imshow(array):
    b,g,r = cv2.split(array)
    rgb = cv2.merge([r,g,b])
    surface1 = pygame.surfarray.make_surface(rgb)
    surface2 = pygame.transform.rotate(surface1, -90)
    surface3 = pygame.transform.flip(surface2, TrueFalse)
    screen.blit(surface3, (0,0))
    pygame.display.flip()

cascade_path = '../haarcascades/haarcascade_frontalface_alt.xml'
cascade = cv2.CascadeClassifier(cascade_path)
with picamera.PiCamera() as camera:
    with picamera.array.PiRGBArray(camera) as stream:
        camera.resolution = (320240)
        while True:
            # stream.arrayにBGRの順で映像データを格納
            camera.capture(stream, 'bgr', use_video_port=True)

            # 映像データをグレースケール画像grayに変換
            gray = cv2.cvtColor(stream.array, cv2.COLOR_BGR2GRAY)

            # grayから顔を探す
            facerect = cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=2, minSize=(30,30), maxSize=(150,150))
            if len(facerect) > 0:
                for rect in facerect:
                    # 元の画像(system.array)の顔がある位置赤い四角を描画
                    # rect[0:2]:長方形の左上の座標, rect[2:4]:長方形の横と高さ
                    # rect[0:2]+rect[2:4]:長方形の右下の座標
                    cv2.rectangle(stream.array, tuple(rect[0:2]),tuple(rect[0:2]+rect[2:4]), (0,0,255), thickness=2)
                break;
            # pygameで画像を表示
            pygame_imshow(stream.array)
            # "q"を入力でアプリケーション終了
            for e in pygame.event.get():
                if e.type == pygame.KEYDOWN:
                    if e.key == pygame.K_q:
                        pygame.quit()
                        sys.exit()
            # streamをリセット
            stream.seek(0)
            stream.truncate()
    print("写真撮影しました")
    camera.capture('./dst/face_' + datetime.now().strftime('%Y%m%d%H%M%S') + '.jpg')

pygame

pygame は、ビデオゲームを製作するために設計されたクロスプラットフォームのPythonモジュール集。
Pythonでコンピュータグラフィクスと音声を扱うためのライブラリを含んでいる。

https://www.pygame.org/

Open CVのサンプルコード集でした。
参考にどうぞ。

最後まで、読んで頂き、感謝です(ぺこり)

#画像処理
#サンプルコード
#OpenCV
#python
#shinmr

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