werry-chanの日記.料理とエンジニアリング

料理!コーディング!研究!日常!飯!うんち!睡眠!人間の全て!

印鑑の透過画像化・電子印鑑化

印鑑を持ち歩くのも時代的におかしいです.

とっとと電子化してしまった方が世のためです.

今回は印鑑を透過画像化してPC上で印鑑を押せるようにしてしまおうという記事です.

具体的には
f:id:werry-chan:20200211011801j:plain
↑このような画像を


f:id:werry-chan:20200211015247p:plain
↑このような透過画像にしてしまうということです.
(佐藤さんが世間で一番多い苗字なので,「佐藤」の印鑑を利用しました.)


いくつか作り方がありますが,今回はpython+OpenCVによる方法を紹介させてもらいます.

ネット上に転がってる画像処理サイトに,印鑑という個人証明印を送ることは,セキュリティ上良くないと思う方がいるだろうと言うことでこの方法について話していきます.
(もう少し簡単な方法で,MicrosoftのOfficeの画像処理を利用する方法もありますが,その話はこちら↓です.)
Wordで背景透過画像作成(印鑑の透過画像化・電子印鑑化2) - werry-chanの日記.料理とエンジニアリング


「コードだけ見せろ,あとは自分で考えるぜ!」って方は,最後にまとめたコードを載せてありますので,記事の最後までスクロールしてください.




今回使ったのはPython3+numpy+OpenCVです.


今回作成する目的は,印鑑の透過画像ということですので,赤色領域の抽出を行います.

まずは,赤色抽出の関数を以下のようにして定義します.

関数の引数は,img: 入力カラー画像 min_st: 彩度の下限値 max_st: 彩度の上限値 min_br: 明度の下限値 max: 明度の上限値 となっています.

import numpy as np
import cv2


def detect_red_color(img, min_st, max_st, min_br, max_br):
    # Convert BGR to HSV
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # define range of red color in HSV
    hsv_min = np.array([0, min_st, min_br])
    hsv_max = np.array([30, max_st, max_br])
    mask1 = cv2.inRange(hsv, hsv_min, hsv_max)

    # define range of red color in HSV
    hsv_min = np.array([150, min_st, min_br])
    hsv_max = np.array([179, max_st, max_br])

    # Threshold the HSV image to get only red colors
    mask2 = cv2.inRange(hsv, hsv_min, hsv_max)

    # Sum red colors 
    mask = mask1 + mask2

    # Bitwise-AND mask and original image
    masked_img = cv2.bitwise_and(img, img, mask=mask)

    return mask, masked_img


関数内の処理の説明です.

cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

↑RGB画像をHSV画像に変換しています.

    # define range of red color in HSV
    hsv_min = np.array([0, min_st, min_br])
    hsv_max = np.array([30, max_st, max_br])
    # Threshold the HSV image to get only red colors
    mask1 = cv2.inRange(hsv, hsv_min, hsv_max)

    # define range of red color in HSV
    hsv_min = np.array([150, min_st, min_br])
    hsv_max = np.array([179, max_st, max_br])
    # Threshold the HSV image to get only red colors
    mask2 = cv2.inRange(hsv, hsv_min, hsv_max)

HSV色空間上の赤色領域を抽出する準備をしています.

HSV色空間では,画像は色相・彩度・明度で表します.

1つのピクセル単位に[色相, 彩度, 明度]という形で,範囲は[0-179, 0-255, 0-255]で入れられています.

今回は赤色の色相を抽出したいので,赤色の色相である0-30&150-179を抽出するように,mask1とmask2の2つに分けて書いてあります.

cv2.inRange(hsv, hsv_min, hsv_max)で指定した範囲のみを抽出した画像(mask画像)を作成してます.

    # Sum red colors 
    mask = mask1 + mask2

    # Bitwise-AND mask and original image
    masked_img = cv2.bitwise_and(img, img, mask=mask)

↑先ほど作成したmask1とmask2を合算して,cv2.bitwise_and(img, img, mask=mask)で赤色領域以外は値0となる画像を作成しています.


ここまでの処理で,印鑑領域の赤色のみを抽出できます.

画像の変化の様子を追って見て見ましょう.

引数はmin_st =100 , max_st = 255, min_br = 150, max_br = 255の実行結果です.

初めの画像(img)
f:id:werry-chan:20200211011801j:plain
(透過画像がない.jpg画像をあえて使ってます.)


赤色領域のマスク画像(mask)
f:id:werry-chan:20200211011833p:plain


赤色領域の抽出画像(masked_img)
f:id:werry-chan:20200211011902p:plain



これらの画像では電子印鑑としては不十分です.

なぜなら,背景の黒色領域が透過していないので,このままでは全く使い物になりません.

それでは次に,黒色領域を透過状態にしましょう.

img = cv2.imread("inkan.jpg")
red_mask, red_masked_img = detect_red_color(img,100,255,150,255)


red_mask = red_mask[:,:,np.newaxis]
# add alpha channel 
red_masked_img_alpha = np.concatenate([red_masked_img, red_mask], 2)

先ほど作成した関数を用いて,透過画像を作成しています.

赤色領域のマスク画像(red_mask)の値をalpha値として利用しています.

red_mask = red_mask[:,:,np.newaxis]

↑この処理によって2次元のマスク画像を3次元化して,

red_masked_img_alpha = np.concatenate([red_masked_img, red_mask], 2)

↑赤色抽出カ画像(3次元画像)に透過値(4要素目)を追加しています.

通常のカラー画像は[青, 緑, 赤]の3要素であるのに対して,

透過画像は[青, 緑, 赤, 透過値(範囲: 0-255)]の4要素になっています.

これらの処理によって透過画像が作成されます.

元画像(img)
f:id:werry-chan:20200211011801j:plain


透過画像(red_masked_img_alpha)
f:id:werry-chan:20200211014012p:plain
(透過画像は,alpha値を格納できる.pngで保存しましょう..jpgでは透過されません.)



この状態なら十分に電子印鑑として利用できそうですね.

しかしながら,撮影条件(部屋の明るさ・紙の状態)によっては不十分な結果しか得られないかもしれません.

そのため追加で,彩度・明度の補正を行なっていきましょう.

引数は,img: 入力画像 st_mag: 彩度の倍率 br_mag: 明度の倍率です.

def hsv_magnification(img,st_mag,br_mag):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    hsv[:, :, (1)] = hsv[:, :, (1)]*st_mag  # multiple saturation
    hsv[:, :, (2)] = hsv[:, :, (2)]*br_mag  # multiple brightness
    bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
    return bgr

先ほどまでの処理でも用いたHSV色変換を利用しています.

どの程度の変化が見られるか確認して見ましょう.


彩度・明度の変化なしの透過画像.
f:id:werry-chan:20200211014012p:plain


彩度1.2倍・明度1.0倍の透過画像
f:id:werry-chan:20200211015247p:plain


若干ですが赤味が上昇していることが見て取れますね.



これらの処理を全てまとめたコードを最後に置いておきましょう.

import numpy as np
import cv2


def detect_red_color(img,min_st,max_st,min_br,max_br):
    # Convert BGR to HSV
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # define range of red color in HSV
    hsv_min = np.array([0, min_st, min_br])
    hsv_max = np.array([30, max_st, max_br])
    mask1 = cv2.inRange(hsv, hsv_min, hsv_max)

    # define range of red color in HSV
    hsv_min = np.array([150, min_st, min_br])
    hsv_max = np.array([179, max_st, max_br])

    # Threshold the HSV image to get only red colors
    mask2 = cv2.inRange(hsv, hsv_min, hsv_max)

    # Sum red colors 
    mask = mask1 + mask2

    # Bitwise-AND mask and original image
    masked_img = cv2.bitwise_and(img, img, mask=mask)

    return mask, masked_img


def hsv_magnification(img,st_mag,br_mag):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    hsv[:, :, (1)] = hsv[:, :, (1)]*st_mag  # multiple saturation
    hsv[:, :, (2)] = hsv[:, :, (2)]*br_mag  # multiple brightness
    bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
    return bgr


img = cv2.imread("inkan.jpg")
red_mask, red_masked_img = detect_red_color(img,100,255,150,255)
cv2.imwrite("red_masked_img.png", red_masked_img)

red_masked_img = hsv_magnification(red_masked_img, 1.2, 1.0)
red_mask = red_mask[:,:,np.newaxis]
red_masked_img_alpha = np.concatenate([red_masked_img, red_mask], 2)

cv2.imwrite("red_mask.png", red_mask)
cv2.imwrite("red_masked_img_alpha.png",red_masked_img_alpha)


↓参考サイトです.
色空間の変換 — OpenCV-Python Tutorials 1 documentation
画像の彩度、明度を変える - Pythonでいろいろやってみる