メインコンテンツへスキップ
見出し画像

Pythonライブラリ(画像処理):OpenCV_基礎編

    1.概要

     OpenCV(Open Source Computer Vision Library)は画像処理に特化したライブラリです(詳細は公式チュートリアル参照)。OpenCVは様々なアルゴリズムがありますが今回の記事では基礎処理をメインに紹介します。

     なおVersionによってAPIが大きく変わるため、エラー発生時は適宜調べていく必要がありますのでご留意ください。

    【シンプルなエラー】
    ●ファイル名が日本語(ファイル名は英語にしておく方が無難)

    【参考:色に関して】
     画像の色に関する情報を「非構造化データ解析」から抜粋しました。

    画像

    2.環境構築

     まずは環境構築を実施します。OSや使用環境にも依存すると思うのでエラーが出た場合は公式Docsをご確認ください。

    2-1.Windows

     私の環境下(Windows10/11+Anaconda)ではTerminalからOpenCVパッケージをインストールするだけで使用できました。

    [Terminal]
    pip install opencv-python

     インストール後に下記実行してエラーが出なければ完了です。

    [IN]
    import cv2
    print(cv2.__version__)
    
    [OUT]
    4.5.3

    2-2.Linux(Raspberry Pi)

    追って

    3.OpenCVの基礎関数

     OpenCVで使用できる一般的な関数を紹介します。

    3-1.時間の計測:cv2.getTickFrequency()

     時間の計測はcv2.getTickCount()でPCのクロック単位の時間を測定して、cv2.getTickFrequency()でクロック周波数から時間に変更します(詳細は公式)。

    [IN]
    e1 = cv2.getTickCount()
    
    for i in range(1000):
        pass
    
    e2 = cv2.getTickCount()
    time = (e2 - e1)/ cv2.getTickFrequency()
    print(time, 'sec')
    
    [OUT]
    0.0001109 sec

     なお時間計測はtimeやJuypterの%timeitでも計測可能です。

    4.OpenCVの基本操作:画像編

     OpenCVの基本的なメソッドを紹介します。詳細は下記参照しました。

    4-1.画像ファイルの読み込み:cv2.imread()

     ファイルの読み込みはcv2.imread('filepath', <出力色>)を使用します。

    【cv2.imread()の注意点】
    ●第2引数がデフォルトの場合データはBGRで出力される
     ー>RGBではなくPillowと異なるため注意
     ー>第2引数が0の場合はグレースケールとなる
    ●出力はnumpyのarray型で出力される
    ●画像データ/Numpyの特性より255-imgでネガポジ反転処理が可能

    [IN]
    import cv2
    import matplotlib.pyplot as plt
    import japanize_matplotlib
    
    imgfile = "konan.jpg" #画像ファイルパス
    
    img_bgr = cv2.imread(imgfile)
    img_0 = cv2.imread(imgfile, 0) #グレースケールで読み込む
    
    print(type(img_bgr), img_bgr.shape)
    print(img_bgr)
    
    plt.imshow(img_bgr)
    plt.imshow(255-img_bgr)
    plt.imshow(img_0)
    
    [OUT]
    <class 'numpy.ndarray'> (648, 484, 3)
    
    [[[127 109 110]
      [124 106 107]
      [127 107 106]
      ...
      ...
      [ 42  56  68]
      [ 46  60  72]
      [ 41  55  67]]]
    画像

    4-2.画像の保存:cv2.imwrite()

     画像を保存する場合はcv2.imwrite('filepath', データ)となります。

    [IN]
    cv2.imwrite('konan_copy.jpg', img_bgr)
    
    [OUT]
    ディレクトリに”konan_copy.jpg”のファイルが作成されます

    4-3.画像の出力:cv2.imshow()

     画像を出力する場合cv2.imshow(<Window名※英語のみ>, imgデータ)を使用します。出力はウィンドウとしてポップアップで出力されるため、今後はJupyerで見やすいplt.imshow()の方を使用していきます。

    【cv2.imshow()のオプション】
    ●cv2.waitKey():ウィンドウの表示時間[ms]を指定する(0は無限時間)
    ●cv2.destroyAllWindows():現在までに作られた全てのウィンドウを閉じる
    ●cv2.namedWindow():ウィンドウサイズの調整可能

    [IN]
    import cv2
    
    imgfile = "konan.jpg" #画像ファイルパス
    img_bgr = cv2.imread(imgfile)
    
    cv2.imshow('window title', img_bgr) #画像を表示
    cv2.waitKey(0) #0を指定すると閉じるボタンを押すまで起動し続ける
    
    [OUT]
    画像
    [IN]
    import cv2
    
    imgfile = "konan.jpg" #画像ファイルパス
    img_bgr = cv2.imread(imgfile)
    
    cv2.namedWindow('window title', cv2.WINDOW_NORMAL) #Windowサイズを指定
    cv2.imshow('window title', img_bgr) #画像を表示
    cv2.waitKey(0) #無限時間表示
    cv2.destroyAllWindows() #ウィンドウを閉じる
    
    [OUT]
    ウィンドウサイズを手動で調整可能
    画像

    5.画像加工1:配色変換

     公式Docsを参照に画像の配色加工を紹介します。

    なお画像出力用(説明用)に簡単なクラスを作成しました。

    [IN]
    class Showimgs:
        def __init__(self, figh:int , figw: int, w: int, h: int, imgs:list, titles:list):
            self.figh = figh
            self.figw = figw
            self.w = w
            self.h = h
            self.imgs = imgs
            self.titles = titles
            
        def show(self):
            fig = plt.figure(figsize=(self.figh, self.figw))
            
            for i in range(self.w):
                for j in range(self.h):
                    _ = fig.add_subplot(self.w, self.h, i*self.h+j+1)
                    _.imshow(self.imgs[i*self.h+j])
                    _.set_title(self.titles[i*self.h+j])

    5-1.BGR↔RGB変換:cv2.COLOR_BGR2RGB

     cv2.imread()だと画像データがBGRで出力されます。通常(RGB)に戻すにはcv2.cvtColor('BGRデータ', cv2.COLOR_BGR2RGB)を実行します(RGBをBGRに変換に変換する場合は引数に" cv2.COLOR_BGR2RGB"を使用)。
     処理イメージは下図であり3次元に並ぶデータ配列を入れ替えます。

    画像
    [IN]
    import cv2
    import matplotlib.pyplot as plt
    
    imgfile = "konan.jpg" #画像ファイルパス
    img_bgr = cv2.imread(imgfile) #BGRで出力
    img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) #BGRをRGBに変換
    
    #画像確認:並べて出力
    imgs, titles = [img_bgr, img_rgb], ['BGR', 'RGB:cv2.COLOR_BGR2RGB']
    imager= Showimgs(figh=6, figw=12, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    
    [OUT]
    左:オリジナル、右:返還後
    画像

     なおcv2.split()で画像データを(B, G, R)に分割してcv2.merge()で再結合しても同じ結果を取得することが出来ます。
    (※cv2.split()は処理速度は遅いため必要な時以外は使用しない方がよい)

    [IN]
    b, g, r = cv2.split(img_bgr)
    print('Blueの形状:', b.shape, 'Greenの形状:', g.shape, 'Redの形状:', r.shape)
    img_rgb = cv2.merge([r, g, b])
    
    imgs, titles = [img_bgr, img_rgb], ['BGR', 'RGB:cv2.merge([r, g, b])']
    imager= Showimgs(figh=6, figw=12, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像出力は同上

    5-2.グレー変換:cv2.COLOR_BGR2GRAY

     グレースケールデータへの変換はcv2.COLOR_BGR2GRAYを使用します。なお”COLOR_RGB2GRAY”もありますが、下記の通りRGBデータで使用しても同じ結果となりました。

    [IN]
    # グレースケールに変換
    img_gray_bgr = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) #グレー変換 BGRで実施
    img_gray_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) #グレー変換 RGBで実施
    
    imgs, titles = [img_bgr, img_gray_bgr, img_gray_rgb], ['BGR', 'グレー(BGR)', 'グレー(RGB)']
    imager= Showimgs(figh=12, figw=24, w=1, h=3, imgs=imgs, titles=titles)
    imager.show()
    
    画像

    5-3.HSV変換

     HSVとは色相(Hue)、彩度(Saturation)、明度(Value)から画像データを処理します。

    [IN]
    img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) #HSV変換:Hue,Saturation,Value(Brightness)
    
    imgs, titles = [img_bgr, img_hsv], ['BGR', 'HSB:cv2.COLOR_BGR2HSV']
    imager= Showimgs(figh=6, figw=14, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    5-4.色調変換(LAB変換)

     LAB変換は下記の通りです。後述しますが、LAB変換後に閾値をとることで物体認識がしやすくなるため前処理として使用されます。

    [IN]
    img_LAB = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2LAB) #LAB変換:L:輝度、A:赤ー緑、B:青ー黄色
    
    imgs, titles = [img_bgr, img_LAB], ['BGR', 'LAB:cv2.COLOR_RGB2LAB']
    imager= Showimgs(figh=6, figw=14, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    5-5.2値化(しきい値処理):cv2.threshold()

     2値化とは、画像の画素値がしきい値より大きければある値(白)を割り当て、そうでなければ別の値(黒)を割り当てます。

    画像

     処理はcv2.threshold(<img>, <閾値>, <最大値>, <処理>)です。

    【しきい値処理の注意点(公式Docs参照)】
    ●入力画像はグレースケール画像である必要があります
    ●しきい値処理(5種類)の詳細は公式Docs参照

    [IN]
    ret,thresh1 = cv2.threshold(img_rgb,127,255,cv2.THRESH_BINARY)
    ret,thresh2 = cv2.threshold(img_rgb,127,255,cv2.THRESH_BINARY_INV)
    ret,thresh3 = cv2.threshold(img_rgb,127,255,cv2.THRESH_TRUNC)
    ret,thresh4 = cv2.threshold(img_rgb,127,255,cv2.THRESH_TOZERO)
    ret,thresh5 = cv2.threshold(img_rgb,127,255,cv2.THRESH_TOZERO_INV)
    
    imgs = [img_rgb, thresh1, thresh2, thresh3, thresh4, thresh5]
    titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
    imager= Showimgs(figh=10, figw=10, w=2, h=3, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

     なお参考までに公式では下記処理を実施しております。

    画像

    5-6.適応的しきい値処理:cv2.adaptiveThreshold

     前の例ではある画像に対して一つのしきい値を与えてしきい値処理をしましたが,撮影状況などによって画像上の領域ごとに異なる光源環境になってしまっているような画像に対して期待するような結果が得られません。
     このような状況では適応的しきい値処理を使用します。関数のパラメータは下記の通りです。

    【cv2.adaptiveThreshold()のパラメータ】
    1.しきい値の計算方法:小領域中でのしきい値の計算方法
     ー>cv2.ADAPTIVE_THRESH_MEAN_C : 近傍領域の中央値をしきい値とする
     ー>cv2.ADAPTIVE_THRESH_GAUSSIAN_C : 近傍領域の重み付け平均値をしきい値とする。重みの値はGaussian分布になるように計算されます.
    2.Block Size:しきい値計算に使用する近傍領域のサイズであり、1より大きい奇数を指定する必要がある
    3.C:計算されたしきい値から引く定数

    [IN]
    img_blur = cv2.medianBlur(img_gray_bgr,5)
    
    ret,th1 = cv2.threshold(img_blur,127,255,cv2.THRESH_BINARY)
    th2 = cv2.adaptiveThreshold(img_blur,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,11,2)
    th3 = cv2.adaptiveThreshold(img_blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
    
    imgs = [img_blur, th1, th2, th3]
    titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
    imager= Showimgs(figh=10, figw=10, w=2, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    5-7.ぼかし(平滑化)・ノイズ除去

     ぼかし処理では特定サイズの画素の値を平均化することで全体をなだらかにする処理です(詳細は公式参照)。

    画像

     ぼかし処理は複数(cv2.blur(), cv2.medianBlur()など)ありますが、ガウシアンフィルターはcv2.GaussianBlurを使用します。
     ノイズ除去はcv2.fastNlMeansDenoisingColoredを使用します。

    [IN]
    img_Blur = cv2.GaussianBlur(img_rgb, (15,15), 0) #ぼかし
    img_Denoise = cv2.fastNlMeansDenoisingColored(img_rgb) #ノイズ除去
    
    imgs, titles = [img_rgb, img_Blur, img_Denoise], ['オリジナル', 'ぼかし', 'ノイズ除去']
    imager= Showimgs(figh=10, figw=12, w=1, h=3, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    6.画像加工2:形状加工(幾何変換)

     公式Docsを参照して画像の幾何変換を紹介していきます。

    6-1.トリミング

     トリミングとは画像の一部分を切り取る処理です。

    【トリミングの要領】
    1.cv2.imread()でnumpy型のarrayを取得
    2.スライス処理でarrayデータを部分的に抽出※次元と画像の向きに注意
    3.スライスしたデータで画像処理

    [IN]
    import cv2
    import matplotlib.pyplot as plt
    
    print('画像サイズ', img_rgb.shape)
    print('切取りサイズ:', img_rgb.shape[0]//2, img_rgb.shape[1]//2)
    
    #トリミング:ファイルサイズからスライスで抽出
    img_trimLT = img_rgb[:img_rgb.shape[0]//2, :img_rgb.shape[1]//2] 
    img_trimRT = img_rgb[:img_rgb.shape[0]//2, img_rgb.shape[1]//2:] 
    img_trimLB = img_rgb[img_rgb.shape[0]//2:, :img_rgb.shape[1]//2] 
    img_trimRB = img_rgb[img_rgb.shape[0]//2:, img_rgb.shape[1]//2:] 
    
    
    imgs = [img_trimLT, img_trimRT, img_trimLB, img_trimRB]
    titles = [f'img_trimLT(:{img_rgb.shape[0]//2}, :{img_rgb.shape[1]//2})', f'img_trimRT(:{img_rgb.shape[0]//2}, {img_rgb.shape[1]//2}:)', f'img_trimLB({img_rgb.shape[0]//2}:, :{img_rgb.shape[1]//2})', f'img_trimRB({img_rgb.shape[0]//2}:, {img_rgb.shape[1]//2}:)']
    imager= Showimgs(figh=8, figw=8, w=2, h=2, imgs=imgs, titles=titles)
    imager.show()
    画像

    6-2.リサイズ/モザイク処理:cv2.resize()

     画像の拡大・縮小(リサイズ)はcv2.resize()で実行できます。縮小後に拡大するとモザイク処理と同等の処理がかかります。

    [IN]
    img_big = cv2.resize(img_rgb, (img_rgb.shape[0]*2, img_rgb.shape[1]*2)) #リサイズ(倍):ファイルサイズから等倍で調整
    img_small = cv2.resize(img_rgb, (img_rgb.shape[0]//10, img_rgb.shape[1]//10)) #リサイズ(倍):ファイルサイズから等倍で調整
    img_mozaic = cv2.resize(img_small, (img_small.shape[0]*2, img_small.shape[1]*2)) #縮小した画像を2倍に拡大
    
    imgs, titles = [img_rgb, img_big, img_small, img_mozaic], ['オリジナル', '拡大(2倍)', '縮小(1/2)', '縮小->拡大']
    imager= Showimgs(figh=8, figw=10, w=2, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

     モザイク処理を関数化した例は下記の通りです。

    [IN]
    def mosaic(img, x, y, w, h, size):
        # モザイクをかける領域を取得
        (x1, y1, x2, y2) = (x, y, x+w, y+h) #モザイク処理をかける領域を指定
        
        img_rec = img[y1:y2, x1:x2] #スライスでモザイク処理をかける領域を取得
        
        # モザイク処理:縮小ー>拡大
        img_small = cv2.resize(img_rec, (size, size)) #sizeと同じ値に画像を縮小
        img_mos = cv2.resize(img_small, (w, h), interpolation=cv2.INTER_AREA) #元のサイズに拡大
        # 画像にモザイク画像を重ねる
        img2 = img.copy()
        img2[y1:y2, x1:x2] = img_mos #スライス機能で値を上書き
        return img2
    
    img_mosaic10 = mosaic(img=img_rgb, x=50, y=200, w=200, h=300, size=10)
    img_mosaic30 = mosaic(img=img_rgb, x=50, y=200, w=200, h=300, size=30)
    
    imgs, titles = [img_rgb, img_mosaic10, img_mosaic30], ['オリジナル', 'モザイク処理:10', 'モザイク処理:100']
    imager= Showimgs(figh=10, figw=14, w=1, h=3, imgs=imgs, titles=titles)
    imager.show()
    
    
    [OUT]
    ※オリジナル写真は手動で加工
    画像

    6-3.回転:cv2.warpAffine

     画像の回転処理は下記の通りです。シンプルに回転させるだけならPillowの方が楽だと思います。

    【OpenCVでの回転処理】
    1.cv2.getRotationMatrix2D(回転の中心, 回転角度 , 拡大倍率)で変換行列を作成する
    2.cv2.warpAffine(imgデータ, 変換行列 , 出力する画像サイズ)で画像処理します

    [IN]
    print('画像サイズ(3次元抜き):', img_rgb.shape[:2])
    
    mat = cv2.getRotationMatrix2D(tuple(np.array(img_rgb.shape[:2])/2), 45, 1.0) #回転前の前処理:画像サイズを取得->tupleのままだと編集できないので一度ndarrayにしてからtupleに戻す
    img_rotate45 = cv2.warpAffine(img_rgb, mat, img_rgb.shape[:2])
    
    mat = cv2.getRotationMatrix2D(tuple(np.array(img_rgb.shape[:2])/2), 135, 0.5) 
    img_rotate135half = cv2.warpAffine(img_rgb, mat, img_rgb.shape[:2])
    
    imgs, titles = [img_rotate45, img_rotate135half], ['45°回転:等倍', '135°回転:1/2']
    imager= Showimgs(figh=10, figw=10, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像サイズ(3次元抜き): (648, 484)
    画像

    6-4.反転:cv2.flip

     画像の反転はcv2.flip(img, <回転の向き>)メソッドを使用します。

    [IN]
    img_flip0 = cv2.flip(img_rgb, 0) #上下反転
    img_flip1 = cv2.flip(img_rgb, 1) #左右反転
    
    imgs, titles = [img_rgb, img_flip0, img_flip1], ['オリジナル', 'img_flip0:上下反転', 'img_flip1:左右反転']
    imager= Showimgs(figh=10, figw=14, w=1, h=3, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    7.画像加工3:応用編

     画像加工の応用を紹介します。

    7-1.輪郭抽出:cv2.findContours()

     物体検出のために輪郭を抽出をすることがありcv2.findContoursメソッドを使用します。

    画像

     7-1ー1.前処理

     輪郭抽出をするために画像に下記のような前処理が必要です。

    【輪郭抽出のための前処理】
    1.cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)でグレースケール処理
    2.cv2.threshold(img, x , y, <処理>)で2値化処理
     ー>処理は”cv2.THRESH_BINARY”や"cv2.THRESH_BINARY_INV"など使用
     ー>参考書によっては事前にcv2.GaussianBlur()処理をしている場合もある
    3.cv2.findContours()で輪郭を抽出
     ー>VersionによりAPIが変わっているため公式Docsの記法とズレがある

     7-1-2.輪郭抽出のコード

     サンプルコードは下記の通りです。なおcv2.findContours()はVersionの違いで動作が異なります(下記記事参照)。

    [IN]
    import cv2
    import matplotlib.pyplot as plt
    
    imgfile = "konan.jpg" #画像ファイルパス
    
    img_bgr = cv2.imread(imgfile) #BGRで出力
    img_gray =cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) #グレースケールで出力
    # img_blur = cv2.GaussianBlur(img_gray, (7, 7), 0) #ぼかし
    
    retval, thresh = cv2.threshold(img_gray, 88, 255, cv2.THRESH_BINARY) #2値化:指定値以上とで値を修正
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #輪郭抽出
    
    img_contour = cv2.drawContours(img_bgr, contours, -1, (0,0,255), 3) #輪郭描画
    
    imgs, titles = [img_rgb, img_gray, thresh, img_contour], ['オリジナル', 'グレースケール', '2値化', '輪郭抽出']
    imager= Showimgs(figh=8, figw=10, w=2, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

     別パターンとして前処理や抽出方法を変えてみます。

    【注意点】
    ●cv2.rectangle()で輪郭を記載する場合は元のデータの上書きになる

    [IN]
    import cv2
    import matplotlib.pyplot as plt
    import japanize_matplotlib
    
    imgfile = "konan.jpg" #画像ファイルパス
    
    img_bgr = cv2.imread(imgfile) #BGRで出力
    img_bgr_origin = img_bgr.copy() #オリジナル画像をコピー
    img_blur = cv2.GaussianBlur(img_gray, (7, 7), 0) #ぼかし
    retval, thresh = cv2.threshold(img_blur, 88, 255, cv2.THRESH_BINARY_INV) #2値化:指定値以上とで値を修正
    contours = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0] #輪郭抽出
    print(type(contours), len(contours))
    
    for pt in contours:
        x, y, w, h = cv2.boundingRect(pt)
        # 大きすぎたり小さすぎたり領域を除去
        if w<50 or w>200:continue
        print('輪郭:', x,y,w,h) # 結果を出力
        cv2.rectangle(img_bgr, (x,y), (x+w, y+h), (0,255,0),2 )
    
    img_counter = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) #BGRで出力
    
    imgs, titles = [img_bgr_origin, img_gray, thresh, img_counter], ['オリジナル', 'グレースケール', '2値化', '輪郭抽出']
    imager= Showimgs(figh=8, figw=10, w=2, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    
    [OUT]
    <class 'list'> 51
    輪郭: 303 469 88 53
    輪郭: 135 139 191 290
    画像

    7-2.画像合成1:cv2.addWeighted()

     OpenCVでは画像の算術演算が可能であり画像同士の計算(足し算など)も可能です。ここでは画像合成を実施しました。

    [IN]
    import cv2
    import matplotlib.pyplot as plt 
    
    img1 = cv2.imread('snapshot/649.0.jpg')
    img2 = cv2.imread('konan.jpg')
    rows,cols,channels = img2.shape #画像チャネルデータを取得
    
    img1 = cv2.resize(img1, (cols, rows)) # 画像サイズを合わせる
    
    print('img1調整後画像サイズ:', img1.shape, 'img2画像サイズ:', img2.shape)
    dst = cv2.addWeighted(img1,0.5,img2,0.5,0) #画像合成
    
    plt.imshow(dst)
    plt.axis('off')
    
    [OUT]
    画像

    7-3.画像合成2:ビット単位での処理

     下図のように画像の上にビット単位で別の画像を載せることも可能です。原理としては画像処理をしてマスク画像(下図左)を作成して、得られた領域をcv2.add(mask画像(黒背景), 指定エリア)で合成します。

    画像

     本コードの注意点は下記の通りです。

    【本コードの注意点】
    ●公式チュートリアルのままだとエラーがでたため「opencv画像の算術演算」を参考に追加画像(ロゴ)のresize処理を追加しました。
    ●今回追加したロゴは下図Logo1を使用しました。Logo2(白背景)ではうまく輪郭抽出できなかったためビット処理はできませんでした。

    画像
    [IN]
    img1 = cv2.imread('konan.JPG')
    img2 = cv2.imread('logo.jpg')
    img_resize = cv2.resize(img2, dsize=None, fx=0.5, fy=0.5) #画像サイズを調整
    
    #注目領域(ROI)(挿入エリア)を作成(左上の角)
    rows,cols,channels = img_resize.shape
    roi = img1[0:rows, 0:cols ] #注目領域(ROI)の作成
    
    #マスク、インバースマスクの作成
    img2gray = cv2.cvtColor(img_resize,cv2.COLOR_BGR2GRAY)
    ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
    mask_inv = cv2.bitwise_not(mask)
    
    #ロゴ画像の注目領域(ROI)を黒くする
    img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
    
    #ロゴ画像からロゴのみ(ROI)を抽出する
    img2_fg = cv2.bitwise_and(img_resize ,img_resize ,mask = mask)
    
    #ROI(置換領域)に追加画像(処理後)を追加
    dst = cv2.add(img1_bg,img2_fg)
    img1[0:rows, 0:cols ] = dst
    
    imgs = [mask, mask_inv, img1_bg, img2_fg, dst, img1]
    titles = ['mask:グレー+2値化', ' mask_inv:cv2.bitwise_not(mask)', 'img1_bg:ロゴ(ROI)を黒色化', 'img2_fg:ロゴからROIのみ抽出', 'dst:合成データ' ,'合成後(Output)']
    imager= Showimgs(figh=10, figw=10, w=2, h=3, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

     ちなみに背景が白色ロゴで処理すると下記の通りです。

    画像

    7-4.Canny法によるエッジ検出:cv2.Canny()

     Canny法とはJohn F. Cannyが1986年に発表したエッジ検出のためのアルゴリズムです。詳細は下記記事参照ください。

    [IN]
    edges = cv2.Canny(img_rgb,100,200)
    
    imgs, titles = [img_rgb, edges], ['オリジナル', 'Edge処理']
    imager= Showimgs(figh=10, figw=14, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    8.OpenCVの基本操作:動画編

     OpenCVで動画処理を実施します。詳細は下記参照しました。

     本章では動画処理を記載します。人物認識もしたいため下記サイトの動画を使用しました。ファイル名は"samplemovie.mp4"として作業ディレクトリと同じパスに保存しています。

      なお動画出力はVS CODEだと不調だったためJupyter labで実行しました。

    8-1.動画読み込み:cv2.VideoCapture()

     動画ファイルの読み込みおよび動画の情報抽出は下記の通りです。

    [IN]
    import cv2
    
    path_movie = r"samplemovie.mp4" #動画ファイルパス
    
    cap = cv2.VideoCapture(path_movie) #動画ファイルを読み込み
    
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) #画像幅
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) #画像高さ
    count = cap.get(cv2.CAP_PROP_FRAME_COUNT) #総フレーム数
    fps = cap.get(cv2.CAP_PROP_FPS) #フレームレート(FPS)
    
    print('画像幅:',width, '画像高さ:', height, '総フレーム数:',count, 'FPS:', fps)
    
    [OUT]
    画像幅: 640.0 画像高さ: 360.0 総フレーム数: 649.0 FPS: 29.97002997002997

    8ー2.動画の再生:cv2.imshow()

     動画再生の手順は下記の通りです。Outputの通りVSCODE ではなくJupyter Labを使用しました。

    【動画出力の要領】
    1. cv2.VideoCapture()で動画の情報を取得
    2.cap.read()でフレーム情報を取得
    3.cv2.imshow()で画像表示
    4.cv2.waitKey(<待機時間[s]>)で表示時間を設定
     ー>待機時間=0の場合はウィンドウを閉じるまで表示
     ー>ord("q")により、キーボードの”q”を押すことでウィンドウを閉じる

    [IN]
    import cv2
    
    path_movie = r"samplemovie.mp4" #動画ファイルパス
    
    cap = cv2.VideoCapture(path_movie) #動画ファイルを読み込み
    
    while(cap.isOpened()):
        ret, frame = cap.read()
        if ret:
            cv2.imshow("frame", frame)
        if cv2.waitKey(1) % 0xFF ==ord("q"):
            break
            
    cap.release()
    cv2.destroyAllWindows()
    
    [OUT]
    画像

    8ー3.動画の画像分割:cv2.imwrite()

     動画のフレームごとの写真を取得する方法は各フレーム情報を取得してcv2.imwrite()で保存していきました。

    [IN]
    import os
    if os.path.exists('snapshot') == False:
        os.mkdir('snapshot')
    
    while(cap.isOpened()):
        ret, frame = cap.read()
        if ret:
            cv2.imshow("frame", frame)
            filepath = 'snapshot/' + str(cap.get(cv2.CAP_PROP_POS_FRAMES)) + '.jpg'
            cv2.imwrite(filepath, frame)
            if cv2.waitKey(1) % 0xFF ==ord("q"):
                break
            
    cap.release()
    cv2.destroyAllWindows()
    
    [OUT]
    画像

    8-4.動画の保存:cv2.VideoWriter()

     写真として一枚ごとに取得する場合は前述の通りcv2.imwrite()で処理しますが、動画の場合はcv2.VideoWrite()を使用します。注意点は下記の通りです。

    【動画保存に関する注意点】
    ●Webカメラを保存するならcap = cv2.VideoCapture(0)を使用
    ●cv2.VideoWriter_fourcc(*<コーデック>)を選択する
    ー>公式チュートリアルよりWindowsでは”DIVX”を使用しました。
    ●動画を処理する場合、cv2.VideoWriter(<保存ファイル名>,<コーデックインスタンス>, <fps>, (幅, 高さ))の第4引数は適当な数値を入れるとエラーになる。
     ー>cap.get()でサイズ情報を取得して、整数に変換してから引数に渡す
    ●out.write()を使用することで動画ファイルに書き込みされる。

    [IN]
    import cv2
    import numpy as np
    
    path_movie = r"samplemovie.mp4" #動画ファイルパス
    cap = cv2.VideoCapture(path_movie) #動画ファイルを読み込み
    # cap = cv2.VideoCapture(0) #Webカメラ用
    
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) #画像幅
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) #画像高さ
    
    #コーデック定義+VideoWriterオブジェクト作成
    fourcc = cv2.VideoWriter_fourcc(*'DIVX')
    out = cv2.VideoWriter('output_movie.mp4',fourcc, 20.0, (int(width), int(height)))
    
    while(cap.isOpened()):
        ret, frame = cap.read() #フレームを取得
        
        if ret==True:
            frame = cv2.flip(frame,0) #上下反転
    
            # VideoWriterにフレームを書き込む
            out.write(frame)
            
            # cv2.namedWindow('flipped Movie', cv2.WINDOW_NORMAL) #Windowサイズを指定
            cv2.imshow('flipped Movie',frame)
            if cv2.waitKey(1) % 0xFF == ord('q'):
                break
        else:
            break
    
    # 解放処理
    cap.release()
    out.release()
    cv2.destroyAllWindows()
    
    [OUT] 
    画像

    9.動画処理1:Webカメラ編

     Webカメラを利用した処理を記載します。詳細は次節で説明しますが指定時間ごとにframeを抽出して表示しているだけであり、そのframeの処理は画像加工(写真データ)と同じです。
     よって配色変換、形状加工、輪郭抽出などは抽出したframeに画像と同じように処理すれば実施可能です。

    画像

    9-1.Webカメラの起動

     Webカメラ起動はcv2.VideoCapture(0)で画面を取得してcap.read()で取得したフレームをcv2.imshow()で読み込みます。
     なおエラーが発生した場合の参考対応例は下記の通りです。

    【エラー時の確認事項】
    ●前に処理したタスクが残っている->タスクマネジャーで削除
    ●Windos設定のカメラがOFFになっている->デバイス設定でONにする
    ●複数のカメラがある(参考記事)->引数を0->1に変更する

    [IN]
    import cv2
    import numpy as np
    
    cap = cv2.VideoCapture(0) #Webカメラを開く
    
    while True:
        ret , frame = cap.read() # カメラから画像を取得
        frame =cv2.resize(frame, (500,300)) # 画像サイズを変更
        cv2.imshow('OpenCV Web Camera', frame) # 画面に表示
        k = cv2.waitKey(1) # 1msec待つ
        if k ==27 or k ==13:
            break # ESC or Enterを押したらループ終了
            
    cap.release()
    cv2.destroyAllWindows()
    
    [OUT]
    ウィンドウが立ち上がりWebカメラがみえます。

    10.OpenCVによる顔検出

     OpenCVを使用した技術の一つとして有名なのが顔検出です。本章では顔検出スクリプトを実装します。

    10-1.環境構築

     OpenCVで顔検出する場合はライブラリだけではなく事前学習されたモデル(検出器)を別途用意する必要があります。手順は下記の通りです。

    【環境構築の手順】
    1.OpenCVのGitHubからコードをダウンロード(下図左)して展開する
    2.フォルダから"opencv-4.x/data/haarcascades"へ移動
    3.「haarcascade_frontalface_alt.xml」を作業ディレクトリにコピペする
    ※”haarcascade_frontalface_alt”は顔正面を検出するモデル

    画像

    10-2.顔検出コード

     顔検出コードを作成します。処理としては①前処理(グレースケール化)、②cascade.detectMultiScale()で顔を検出して座標を出力、③取得した顔座標にcv2.rectangle()で枠線を引く 流れとなります。

     なお画像は7-3節で処理した人物画像”snapshot/649.0.jpg”を使用しました。本コードの注意点は下記の通りです。

    【コードの注意点】
    ●Jupyterで実行する場合、顔が検出されなかった場合はquit()の処理がかかりKernelが死ぬため毎回再起動が必要->不要であればquit()をコメントアウト
    ●cascade.detectMultiScale()のminSizeは50以上だと検出されず、8以下にしても右側の女性の顔が認識されなかった。
    ->検出器を変えたりグレースケール以外の処理を加えると検出されるかも

    [IN]
    import cv2
    import matplotlib.pyplot as plt 
    
    
    #カスケードファイルから分類器(検出器)を作成
    cascade_file = "haarcascade_frontalface_alt.xml"
    cascade = cv2.CascadeClassifier(cascade_file)
    
    #画像ファイルにグレースケール処理   
    filepath = "snapshot/649.0.jpg"
    img= cv2.imread(filepath) #BGR
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #グレースケール
    
    #顔認識
    face_list = cascade.detectMultiScale(img_gray, minSize=(30,30))
    #結果表示
    if len(face_list) ==0:
        print('失敗')
        # quit()
    
    img_copy = img.copy() #オリジナル画像をコピー※cv2.rectangle()で上書きされるため
    
    for (x,y,w,h) in face_list:
        print("顔の座標=", x,y,w,h)
        color_frame = (0,255,0) #枠の色{緑色:(0,255,0), 赤色:(0,0,255), 青色:(255,0,0)}
        pen_w = 3 #枠の太さ
        cv2.rectangle(img_copy, (x,y), (x+w, y+h), color_frame, thickness = pen_w)
    
    
    imgs, titles = [img, img_copy], ['オリジナル', '顔認識']
    imager= Showimgs(figh=10, figw=10, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    顔の座標= 70 58 44 44
    画像

    10-3.顔検出+モザイク処理

     本節では顔座標取得までは同じですが、cv2.rectangle()を使用せず座標を用いて5-2節で紹介したモザイク処理をかけました。

    [IN]
    import cv2
    import matplotlib.pyplot as plt 
    
    #カスケードファイルから分類器(検出器)を作成
    cascade_file = "haarcascade_frontalface_alt.xml"
    cascade = cv2.CascadeClassifier(cascade_file)
    
    #画像ファイルにグレースケール処理   
    filepath = "snapshot/649.0.jpg"
    img= cv2.imread(filepath) #BGR
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #グレースケール
    
    #顔認識
    face_list = cascade.detectMultiScale(img_gray, minSize=(30,30))
    #結果表示
    if len(face_list) ==0:
        print('失敗')
        # quit()
    
    img_mosaic = img.copy() #モザイク用画像をコピー
    
    for (x,y,w,h) in face_list:
        print("顔の座標=", x,y,w,h)
        img_mosaic = mosaic(img=img, x=x, y=y, w=w, h=h, size=5) #モザイク処理
    
    
    imgs, titles = [img, img_mosaic], ['オリジナル', 'モザイク処理後']
    imager= Showimgs(figh=10, figw=10, w=1, h=2, imgs=imgs, titles=titles)
    imager.show()
    
    [OUT]
    画像

    11.その他

    11-1.OpenCVの描写機能

     OpenCVの機能を使用して円や線を記載できます。詳細は公式に記載しておりますので、ここでは簡単な実演コードだけ記載します。

    [IN]
    import numpy as np
    import datetime
    import cv2
    import matplotlib.pyplot as plt
    
    #赤丸を作成
    def writestamp(width, height):
        
        # Create a black image
        img = np.ones((width, height,3), np.uint8)*255 #(512,512)サイズの白画像を作成
        
        #赤丸を作成(外丸の中に白丸を作成)
        radius = 150 #円の半径
        img = cv2.circle(img,(int(width/2),int(height/2)), radius, (255,0,0), 5) #(x,y)座標、半径、色、線の太さ(-1で塗りつぶし)
            
        #赤色の直線作成
        x1, y1 = int(width/2-radius*np.cos(np.pi*12/180)), int(height/2-radius*np.sin(np.pi*12/180)) #上線の起点と高さ
        x2, y2 = int(width/2+radius*np.cos(np.pi*12/180)), int(height/2+radius*np.sin(np.pi*12/180)) #下線の起点と高さ
        img = cv2.line(img,(x1, y1),(x2, y1),(255,0,0),5) #(x,y)座標、(x,y)座標、色、線の太さ
        img = cv2.line(img,(x1, y2),(x2, y2),(255,0,0),5) #(x,y)座標、(x,y)座標、色、線の太さ
        
        #テキスト追加
        font = cv2.FONT_HERSHEY_SIMPLEX
        today = datetime.datetime.today().strftime("%y.%m.%d")
        cv2.putText(img, today ,(x1,y2-10), font, 2,(255,0,0),2,cv2.LINE_AA) #(x,y)座標、テキスト、フォント、サイズ、色、線の太さ、書式
        #社名・部門名
        x3, y3 = int(width/2-radius*np.cos(np.pi*30/180)/1.8), int(height/2-radius*np.sin(np.pi*30/180)) #上線の起点と高さ
        cv2.putText(img, 'note' ,(x3,y3), font, 2,(255,0,0),2,cv2.LINE_AA) #(x,y)座標、テキスト、フォント、サイズ、色、線の太さ、書式
        #担当者名
        x4, y4 = int(width/2-radius*np.cos(np.pi*30/180)/1.8), int(height/2+radius*np.sin(np.pi*40/180)) #上線の起点と高さ
        cv2.putText(img, 'KIYO' ,(x4,y4), font, 2,(255,0,0),2,cv2.LINE_AA) #(x,y)座標、テキスト、フォント、サイズ、色、線の太さ、書式
        
        return img
    
    img = writestamp(width=512, height=512)
    plt.imshow(img)
    
    
    [OUT]
    画像

    参考1:Linuxコマンド

    1.ファイル/ディレクトリ情報確認:ls

    [Terminal:WSLによるUbuntuから利用]
    #作業ディレクトリ内のファイル一覧を表示
    ls
    #-aオプション:すべて表示
    ls -a

    2.WSLを使用してWindowsのシステムへ接続:cd /mnt/c

    [Terminal:WSLによるUbuntuから利用]
    #Windows側のCドライブに移動(マウント)
    cd /mnt/c
    #Windows側のデスクトップに移動(user名はkiyo)
    cd Users/kiyo/Desktop

    参考2:参考資料

    K-TechLaboゼミ用学習ノート(中村勝則)


    あとがき

     疲れたのでとりあえず公開:適宜追加予定

     これらの最上位スキルを取得できれば様々な動画処理ができるようになって自分がしたいことも何個かできるけど、先が遠すぎる・・・・・


     
     

    KIYO

     
     
    普段は製造業で企画/開発/設計しております。記事はプログラミング・機械学習、IoT関係の記事をメインで作成し、なるべく1つの記事で知りたいことを網羅していきます。内容は学術的より実装・アウトプット(ほしくなるもの)を重視して作成しています。 面白そうな仕事があればやりたいです!

    あなたへのおすすめ