OpenCVにてアルファチャンネル画像をわかりや表示する

OpenCVにて画像処理をする際に、imshow()関数にて結果を確認することがあると思いますが、OpenCVに組み込まれているimshow()関数はアルファチャンネル付きの画像出力をサポートしていません。(アルファチャンネルの値は無視して出力されます。)

ここでは、アルファチャンネル付きの画像をわかりやすく表示する方法について説明していきます。

アルファチャンネルの存在を描画する

本記事にてやりたいことを以下に示します。

opencvにてアルファチャンネル付き画像を表示する例

左写真)アルファチャンネルで透明に指定されている箇所が全て黒
右写真)透明部をチェスボートで描画

左写真では髪の毛と背景の境界がわからなくなってしまっていますね。

OpenCV imshow()の仕様

  • アルファチャンネル付き画像を投げるのは可
  • 表示時はアルファチャンネルを無視
rgba_img = cv2.imread( "rgba.png", cv2.IMREAD_UNCHANGED )   # アルファチャンネル付き画像
cv2.imshow( 'window_name', rgba_image )   # これは可能(しかしαは考慮されない)

アルファチャンネルが無視されるため、透明部は3ch部で指定した色となってしまいます。これでは使いにくいですね。
そこで、透明部をチェスボード柄で描画する関数を自作していきましょう。

アルファチャンネル背景用チェスボードの作成

import cv2
import numpy as np

IMAGE_PATH = "/path/to/the/rgba_image.png"

# 引数shapeと同じサイズのチェスボードを作成する関数
def chessboardGenerator( shape, cell_size=16, color1=(64,64,64), color2=(128,128,128) ):

    # 目標画像より少し大きい、cell_sizeで割り切れるサイズのshapeを作成
    chessboard_shape = ( (shape[0]//cell_size+1)*cell_size, (shape[1]//cell_size+1)*cell_size, 3 )

    # 1色目にて塗りつぶす
    chessboard = np.full( chessboard_shape, color1, dtype=np.uint8)

    # 2色目の1辺がcell_sizeの正方形
    cell = np.full( (cell_size, cell_size, 3), color2, dtype=np.uint8 )

    # チェスボード柄に 該当箇所を2色目の正方形で置き換え
    for cx in range( 0, chessboard_shape[1]//cell_size ):
        for cy in range( 0, chessboard_shape[0]//cell_size ):
            if (cx+cy) % 2: continue
            x, y = cx*cell_size, cy*cell_size
            chessboard[y:y+cell_size, x:x+cell_size, :] = cell

    # 少し大きく作った分を削る
    chessboard = chessboard[:shape[0], :shape[1], :]

    return chessboard

if __name__ == "__main__":
    rgba_image = cv2.imread( IMAGE_PATH, cv2.IMREAD_UNCHANGED )
    chessboard = chessboardGenerator( rgba_image.shape )
    cv2.namedWindow( "chessboard", cv2.WINDOW_NORMAL )
    cv2.imshow( "chessboard", rgba_image )
    cv2.waitKey(0)

これでチェスボードが完成しました。

アルファチェンネルの存在を示唆するチェスボード背景

チェスボード背景をアルファチャンネルの値にしたがってブレンドする

import cv2
import numpy as np

IMAGE_PATH = "/path/to/the/rgba_image.png"

def chessboardGenerator( shape, cell_size=16, color1=(64,64,64), color2=(128,128,128) ):

    chessboard_shape = ( (shape[0]//cell_size+1)*cell_size, (shape[1]//cell_size+1)*cell_size, 3 )

    chessboard = np.full( chessboard_shape, color1, dtype=np.uint8)

    cell = np.full( (cell_size, cell_size, 3), color2, dtype=np.uint8 )

    for cx in range( 0, chessboard_shape[1]//cell_size ):
        for cy in range( 0, chessboard_shape[0]//cell_size ):
            if (cx+cy) % 2: continue
            x, y = cx*cell_size, cy*cell_size
            chessboard[y:y+cell_size, x:x+cell_size, :] = cell

    chessboard = chessboard[:shape[0], :shape[1], :]

    return chessboard


# 背景をブレンドするための関数
def chessboardBG( img ):

    # 入力画像がアルファチャンネルを含まない(4チャンネルでない)場合はそのまま
    if img.shape[2] != 4:
        return img

    # 先ほど作成した関数にてチェスボードを取得
    chessboard = chessboardGenerator( img.shape )

    # ここがメインのブレンドする箇所
    # アルファチャンネルの値がαの時、背景:メインを(1-α);αにて混ぜ合わせる
    chessboard_bg_img = np.array( chessboard*(1-img[:,:,3:]/255) + img[:,:,:3]*(img[:,:,3:]/255), dtype=np.uint8 )

    return chessboard_bg_img


if __name__ == "__main__":
    # cv2.IMREAD_UNCHANGEDにてアルファチャンネルをそのまま読み込む
    rgba_image = cv2.imread( IMAGE_PATH, cv2.IMREAD_UNCHANGED )
    processed_image = chessboardBG( rgba_image )
    cv2.namedWindow( 'processed', cv2.WINDOW_NORMAL )
    cv2.imshow( 'processed', processed_image )
    cv2.waitKey(0)

アルファチャンネルの値にしたがって、背景:メインを(1-α);αにて混ぜ合わせることで、背景にチェスボードを表示させます。

透明背景部にチェスボードを描画した画像

これで、髪の毛の黒い部分と背景の境界がひと目見てわかるようになりました。

今回は背景の透明度がほどんど0,1の二値でしたが、例えばアルファチャンネル透明度が0.5の領域では、背景のチェスボードとメイン画像が1:1でブレンドされる事になり、チェスボードがうっすら表示されることになります。

OpenCVのGUI

OpenCVで画像を弄り回すのは大変便利なんですけど、gui部分は貧弱なので、この点なんとかならないでしょうかね。。。と思う今日この頃です。

以上、OpenCVにてアルファチャンネル画像をわかりや表示する方法についてでした。