【Python・OpenCV】画像の演算 - 足し算・引き算(cv2.Add, cv2.subtract)

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】画像の演算 - 足し算・引き算(cv2.add, cv2.subtract)

2024-02-24

はじめに

OpenCVにも、加算、減算などの算術演算子が用意されています。
これら足し算・引き算は単純な機能ですが、様々な場面で活用されています。

この記事では、足し算を行うcv2.add関数、引き算を行うcv2.subtract関数の使い方や具体的な使用例について紹介します。

(広告) OpenCV関連書籍をAmazonで探す

画像の算術演算

画像処理において、加算、減算などの算術演算子は様々な場面で活用されています。

以下、代表的な用途を紹介します。

  1. 画像の明るさ調整
    • 加算:画像全体を明るくしたい場合に、画像に定数を加算することで明るさを調整できます。
    • 減算:画像全体を暗くしたい場合に、画像から定数を減算することで明るさを調整できます。
  2. 画像の差分抽出
    • 減算:同じシーンを撮影した2枚の画像を減算することで、照明の変化や物体の移動などの差分を抽出できます。
      これは、動体検出や監視カメラの用途に役立ちます。
  3. 画像の合成
    • 加算:複数の画像を重ね合わせ、新たな画像を合成することができます。
      これは、HDR画像の作成やパノラマ画像の作成などに役立ちます。
  4. 画像の反転
    • 減算:画像全体から255を減算することで、画像を反転することができます。
      これは、白黒画像の作成やネガポジ反転などの用途に役立ちます。
  5. 背景差分
    • 減算:背景画像をフレーム画像から減算することで、動いている対象を抽出できます。

画像の足し算 cv2.add関数

cv2.add関数は2つの入力画像の対応する画素位置のピクセル値を単純に加算します。

使い方は下の通りです。

cv2.add(入力画像1, 入力画像2[, dst[, mask[, dtype]]])

引数

名称説明
入力画像1(必須)1つ目の入力画像
入力画像2(必須)2つ目の入力画像
dst(オプション)加算された出力画像。指定された場合、結果がこの変数に格納されます。指定されない場合は、入力画像1と同じサイズ・型で新しい配列が作成されて結果が格納され、戻り値として取得できます。
mask(オプション)画像の特定の領域を指定するマスク。
dtype(オプション)出力画像のデータ型。デフォルトは入力画像1と同じ型です。

ポイント

  • 入力画像1入力画像2は同じサイズ・チャンネル数である必要があります。
  • マスクを指定するとマスク画像中の値が"0"でないピクセルだけで加算が行われます。
  • 出力画像のデータ型を明示的に指定できます。デフォルトでは入力画像と同じ型です。
  • 加算後、飽和するピクセルは最大値でクリッピングされます。

戻り値

加算された出力画像が戻り値となります。

使い方

以下に透かしを入れるサンプルコードを示します。

透かしを入れるのは下の画像です。
サンプルコードでは"add_image1.jpg"としています。

今回入れる透かしは下の画像の文字部分です。
RBGの3チャンネルの画像で、文字部分は白(255, 255, 255)、背景部分は黒(0, 0, 0)としています。
サンプルコードでは"add_image2.jpg"としています。

下に示すサンプルコードの結果はこの様になります。
左から入力画像1、入力画像2、加算された出力画像の順に表示しています。

import cv2
from matplotlib import pyplot as plt

# 入力画像1を読み込む
image1 = cv2.imread("add_image1.jpg")

# BGRのチャンネル並びをRGBの並びに変更(matplotlibで結果を表示するため)
rgb_image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB) 

# 入力画像2を読み込む
image2 = cv2.imread("add_image2.jpg")

# BGRのチャンネル並びをRGBの並びに変更(matplotlibで結果を表示するため)
rgb_image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB) 

# 2つの画像を加算
added = cv2.add(rgb_image1, rgb_image2)

# 結果の可視化
plt.rcParams["figure.figsize"] = [10,4]                             # ウィンドウサイズを設定
title = "cv2.add: codevace.com"
plt.figure(title)                                                   # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)   # 余白を設定
plt.subplot(131)                                                    # 1行3列の1番目の領域にプロットを設定
plt.imshow(rgb_image1)                                              # 入力画像を表示
plt.title('image1')                                                 # 画像タイトル設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す
plt.subplot(132)                                                    # 1行3列の2番目の領域にプロットを設定
plt.imshow(rgb_image2)                                              # 減算による反転画像を表示
plt.title('image2')                                                 # 画像タイトル設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す
plt.subplot(133)                                                    # 1行3列の3番目の領域にプロットを設定
plt.imshow(added)                                                   # cv2.addによる演算後の画像を表示
plt.title('added')                                                  # 画像タイトル設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す
plt.show()

画像の引き算 cv2.subtract関数

cv2.subtract関数は2つの入力画像の対応する画素位置のピクセル値を単純に減算します。

cv2.subtract関数の使い方は下の通りです。

cv2.subtract(入力画像1, 入力画像2[, dst[, mask[, dtype]]])

引数

名称説明
入力画像1(必須)減算の被減数となる入力画像
入力画像2(必須)減算の減数となる入力画像
dst(オプション)減算された出力画像。指定された場合、結果がこの変数に格納されます。指定されない場合は、入力画像1と同じサイズ・型で新しい配列が作成されて結果が格納され、戻り値として取得できます。
mask(オプション)画像の特定の領域を指定するマスク。
dtype(オプション)出力画像のデータ型。デフォルトは入力画像1と同じ型です。

ポイント

  • cv2.add関数とcv2.subtract関数の引数の構成は同じです。
  • 入力画像1入力画像2は同じサイズ・チャンネル数である必要があります。
  • マスクを指定するとマスク画像中の値が"0"でないピクセルだけで減算が行われます。
  • 出力画像のデータ型を明示的に指定できます。デフォルトでは入力画像と同じ型です。
  • 減算後、ピクセル値が0未満になる場合は"0"にクリップされます。

戻り値

減算された出力画像が戻り値となります。

使い方

2つの画像の引き算で、間違い探しを行います。

間違い探しの画像は下の左側です。左の画像を元に少し変更を加えた画像が右になります。

引用元
Watercolor world animal day illustration

異なる箇所が3つあります。
わかりますか?

答えはこちらの赤丸です。

これらが、正しく検出できるか下のサンプルコードで試してみた結果が下になります。
左から入力画像1、入力画像2、出力画像の順に表示しています。

出力画像はcv2.subtract関数の出力をわかりやすくする様にグレースケールに変換後、二値化処理しました。

# 2つの画像を減算
subrtacted = cv2.subtract(image1, image2)
# グレースケールに返還
subrtacted = cv2.cvtColor(subrtacted, cv2.COLOR_BGR2GRAY)
# 二値化で異なる部分を強調、背景が白になる様にcv2.THRESH_BINARY_INVを指定
retval, subrtacted = cv2.threshold(subrtacted, 50, 255, cv2.THRESH_BINARY_INV)

引き算した画像はカラー画像のため、目立ちにくい色合いであることがあります。
それらを強調するためcv2.threshold関数で二値化して、白黒画像にしています。
背景が白い方がわかりやすいと思い、4つ目の引数をcv2.THRESH_BINARY_INVを指定していますが、背景が黒い方が良い場合はcv2.THRESH_BINARYを指定して下さい。
2つ目の引数である閾値は"50"としていますが、入力画像によって変更した方が良いかもしれませんが、小さすぎると正しく検出できないようです。
理由は以下のように推測しています。
入力画像のリンクで入手した、画像ファイルはsvg形式ですが、InkscapeアプリでJPEGに変換しています。
入力画像2もInkscapeで間違い探しの加工後、同様にJPEGに変換していますが、2つの画像は間違いとして加工した部分以外にも画素値の違いがわずかにできてしまうようです。

グレースケール変換、二値化処理については下の記事で紹介しています。

結果画像の検出された箇所を赤丸で示しました。

以下が全体のサンプルコードとなります。

import cv2
from matplotlib import pyplot as plt

# 入力画像1を読み込む
image1 = cv2.imread("subrtact_image1.jpg")

# BGRのチャンネル並びをRGBの並びに変更(matplotlibで結果を表示するため)
rgb_image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB) 

# 入力画像2を読み込む
image2 = cv2.imread("subrtact_image2.jpg")

# BGRのチャンネル並びをRGBの並びに変更(matplotlibで結果を表示するため)
rgb_image2 = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB) 

# 2つの画像を減算
subrtacted = cv2.subtract(image1, image2)
# グレースケールに変換
subrtacted = cv2.cvtColor(subrtacted, cv2.COLOR_BGR2GRAY)
# 二値化で異なる部分を強調、背景が白になる様にcv2.THRESH_BINARY_INVを指定
retval, subrtacted = cv2.threshold(subrtacted, 50, 255, cv2.THRESH_BINARY_INV)

# 結果の可視化
plt.rcParams["figure.figsize"] = [18,7]                             # ウィンドウサイズを設定
title = "cv2.subtract: codevace.com"
plt.figure(title)                                                   # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)   # 余白を設定
plt.subplot(131)                                                    # 1行3列の1番目の領域にプロットを設定
plt.imshow(rgb_image1)                                              # 入力画像を表示
plt.title('image1')                                                 # 画像タイトル設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す
plt.subplot(132)                                                    # 1行3列の2番目の領域にプロットを設定
plt.imshow(rgb_image2)                                              # 減算による反転画像を表示
plt.title('image2')                                                 # 画像タイトル設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す
plt.subplot(133)                                                    # 1行3列の3番目の領域にプロットを設定
plt.imshow(subrtacted, cmap='gray')                                 # 間違い探しの結果の画像を表示
plt.title('subrtacted')                                             # 画像タイトル設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す
plt.show()

異なるデータ型の足し算・引き算

入力画像1入力画像2のデータ型が異なっている場合、cv2.add関数、cv2.subtract関数では通常はエラーになります。

異なるデータ型の場合のエラーを回避するため、以下の方法で対処できます。

  1. dtype引数を使用して、出力画像のデータ型を明示的に指定する。
img1 = img1.astype(np.uint8) 
img2 = img2.astype(np.int16)

dst = cv2.subtract(img1, img2, dtype=np.int16)

この場合、img1がuint8型でimg2がint16型ですが、正常に減算できます。

  1. 一方の画像をもう一方の画像と同じデータ型にキャストする。
img1 = img1.astype(np.uint8)
img2 = img2.astype(np.int16)

# img2をuint8型にキャストする
img2 = img2.astype(np.uint8) 

dst = cv2.subtract(img1, img2)

このように、入力画像を同じデータ型に揃えることで、データ型が異なる場合にも対処できます。

デフォルトではデータ型が同じである前提なので、入力画像の確認は重要となります。

おわりに

画像の足し算、引き算という単純な処理ですが、工夫次第で色々な用途に使うことができます。
いくつかの注意点がありますが、難しいものでは無いと思いますので、この記事を活用して頂けたらと思います。

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

参考リンク

■(広告)Pythonのオススメ書籍■

-Python, プログラミング, 画像処理
-