OpenCVで画像に矩形や半透明の図形を描く方法
「AIの検知結果を画像に重ねて表示したい」「注目してほしい領域を枠で囲みたい」「枠は付けたいけれど、中身が見えなくなるのは困る」。画像処理をしていると、こうした場面がよくあります。
Pythonでは、画像処理ライブラリのOpenCV(cv2)を使うと、数行のコードで画像に図形を描き込めます。この記事では、矩形・直線・ポリゴン(多角形)の描画と、矩形を半透明で塗りつぶす方法、描画したい範囲を切り出すスライシングまでをまとめます。
この記事で分かること
- OpenCVのインストール方法
- 描画を始める前に知っておきたい座標と色の決まりごと
- ポリゴン・矩形・直線の描画方法
- 矩形を半透明で塗りつぶす方法
- 画像の一部を矩形で切り出す方法
- 描画でつまずきやすいポイントと対処法
OpenCVで描画できること
OpenCVは、画像や動画を扱うためのオープンソースのライブラリです。Pythonで使うときはimport cv2と書くため、「OpenCV2」「cv2」と呼ばれることもあります。
Pythonでは画像を「NumPyの配列」として扱います。画像1枚が、縦×横×色のチャンネル(カラー画像なら3つ)という数値の並びになっているイメージです。OpenCVの描画関数は、この配列の数値を直接書き換えることで、画像に線や図形を描き込みます。
インストール
OpenCVはpipで入ります。パッケージ名はopencv-pythonですが、コード内で読み込むときの名前はcv2になる点に注意してください。
pip install opencv-python
NumPyも使いますが、opencv-pythonを入れると一緒にインストールされます。
描画する前に押さえておきたい3つのルール
サンプルコードを動かす前に、初心者がつまずきやすい点を先に整理しておきます。ここを知っておくと「思った場所に描画されない」「色が違う」といったトラブルを避けられます。
座標は左上が原点
OpenCVの画像座標は、画像の左上が原点(0, 0)です。右へ進むほど x が増え、下へ進むほど y が増えます。数学のグラフのように、上へ行くほど y が増えるわけではありません。
矩形を描くときは、左上の座標(x1, y1)と右下の座標(x2, y2)を指定します。
色は「BGR」の順番で指定する
OpenCVの色指定は、一般的なRGBではなく B(青)・G(緑)・R(赤) の順です。このため、サンプルコードにある(255, 0, 0)は赤ではなく 青 になります。赤を描きたい場合は(0, 0, 255)と指定します。
(255, 0, 0)は青(0, 255, 0)は緑(0, 0, 255)は赤
Webデザインなどで使うRGBの値をそのまま渡すと、赤と青が入れ替わってしまいます。色がおかしいと感じたら、まずBGRの順番を疑ってください。
サンプルコード内の変数について
以降のコードに出てくるimage_npは、描画先の画像を表す変数です。事前に画像を読み込んで、NumPyの配列として用意しておく前提になっています。コード内の# image_np is np.arrayというコメントは、その意味です。
同じく、矩形の座標を指定しているx1, y1, x2, y2も、事前に値を決めておく変数(プレースホルダ)です。実際に使うときは、描きたい位置の座標に置き換えてください。たとえば物体検知の結果で得られた枠の座標を入れる、といった使い方になります。
図形を描画する
ここからは図形ごとの描画方法です。どの関数も、渡した画像(配列)を直接書き換えます。元の画像を残したいときは、描画する前にimage_np.copy()でコピーを作っておくと安心です。
塗りつぶされたポリゴンを描画
3つ以上の点を結んだ多角形を塗りつぶしたいときは、cv2.fillConvexPolyを使います。頂点の座標をnp.arrayにまとめて渡します。データ型はnp.int32を指定します。
import cv2
import numpy as np
# image_np is np.array
points = np.array([(100, 100), (200, 100), (300, 200), (0, 200)], np.int32)
color = (255, 0, 0)
cv2.fillConvexPoly(image_np, points, color)
このコードでは、4つの頂点を結んだ台形が青色で塗りつぶされます。
fillConvexPolyは名前のとおり「凸(とつ)ポリゴン」専用です。くぼみのある形(凹ポリゴン)だと正しく塗れないことがあります。そのような形を塗りたい場合は、cv2.fillPolyを検討してください。
矩形を描画
四角形の枠線を描くときは、cv2.rectangleを使います。引数は、描画先の画像、左上の座標、右下の座標、色、線の太さ(ピクセル)の順です。
import cv2
import numpy as np
# image_np is np.array
color = (255, 0, 0)
cv2.rectangle(image_np, (500, 500), (700, 700), color, 4)
この例では、(500, 500) から (700, 700) までの範囲に、太さ4ピクセルの青い枠が描かれます。画像のサイズより外側にはみ出す部分があってもエラーにはならず、画像の範囲内だけが描画されます。
矩形を透過ありで塗りつぶす
検知した領域を色で塗りつぶしたいけれど、中の様子も見えるようにしたい。そんなときは、半透明の矩形を重ねます。
cv2.rectangleの線の太さに-1を指定すると、枠線ではなく塗りつぶしになります。ただし、そのままでは完全に不透明になり、下の画像が見えなくなってしまいます。そこで次の手順を踏みます。
- 元の画像のコピー(
overlay)の作成 overlay側への、塗りつぶした矩形の描画cv2.addWeightedによる、元の画像とoverlayの合成
import cv2
import numpy as np
# image_np is np.array
color=(255,0,0)
overlay = image_np.copy()
# (x1,y1): 左上座標, (x2,y2): 右下座標
overlay = cv2.rectangle(overlay, (x1, y1), (x2, y2), color, thickness=-1)
alpha = 0.2
beta = 1.0 - alpha
image_np = cv2.addWeighted(image_np, beta, overlay, alpha, 0)
alphaが、塗りつぶした色の濃さ(不透明度)です。0.2なら元の画像が8割、塗りつぶし色が2割の割合で混ざります。値を大きくすると色が濃くなり、小さくすると薄くなります。betaは元の画像側の割合で、alphaと足して1になるように計算しています。
直線を描画
2点を結ぶ直線は、cv2.lineで描けます。始点pt1と終点pt2を指定します。
import cv2
import numpy as np
# image_np is np.array
color=(0,0,255)
pt1 = (0,0)
pt2 = (100,100)
image_np = cv2.line(image_np, pt1, pt2, color, thickness=4)
BGR順なので、(0, 0, 255)は赤です。この例では、画像の左上から右下方向へ、太さ4ピクセルの赤い線が引かれます。
描画関数の早見表
ここまでの関数を整理すると、次のとおりです。
| やりたいこと | 関数 | ポイント |
|---|---|---|
| 多角形を塗りつぶす | cv2.fillConvexPoly |
凸ポリゴン専用。頂点はnp.int32で渡す |
| 矩形の枠を描く | cv2.rectangle |
左上と右下の座標を指定する |
| 矩形を塗りつぶす | cv2.rectangle |
線の太さに-1を指定する |
| 半透明にする | cv2.addWeighted |
コピーした画像に描いてから混ぜ合わせる |
| 直線を引く | cv2.line |
始点と終点を指定する |
スライシング
描画とは少し違いますが、画像の一部だけを取り出したい場面もよくあります。たとえば、検知した領域だけを切り出して保存したり、別の処理にかけたりする場合です。画像を任意の矩形で切り出す場合は、次のようにします。
image_np[top:bottom,left:right]
これは、OpenCV固有の機能ではなく、NumPy配列のスライシングという書き方です。順番に注意が必要で、先に縦方向(y)、あとに横方向(x) を書きます。(x, y)の順で座標を指定する描画関数とは逆になるため、混同しやすいところです。
また、bottomとrightの位置は含まれません。たとえばtop:bottomと書くと、topの行からbottomの1つ手前の行までが切り出されます。
切り出した結果は元の画像とメモリを共有しています。切り出した部分に描画や加工をすると、元の画像も一緒に変わります。元の画像を守りたいときは、.copy()を付けてコピーしてから使ってください。
よくある質問とつまずきポイント
赤で描いたのに青く表示されるのはなぜですか
OpenCVの色指定がBGR順だからです。RGBの感覚で(255, 0, 0)と書くと、赤ではなく青になります。赤にしたい場合は(0, 0, 255)と書いてください。
半透明の矩形を描くにはどうすればよいですか
cv2.rectangleだけでは半透明になりません。画像のコピーに塗りつぶした矩形を描き、cv2.addWeightedで元の画像と混ぜ合わせます。alphaの値で透け具合を調整できます。
描画すると元の画像も書き換わりますか
書き換わります。OpenCVの描画関数は、渡した配列を直接更新します。元の画像を残したいときは、事前にcopy()でコピーを作り、そちらに描画してください。
描画したはずの図形が表示されない場合は、何を確認すればよいですか
次の点を確認してください。
- 座標の値(画像サイズの範囲内であること)
- 座標の順番((x, y) の順であること)
- 描画色と背景色(同じ色になっていないこと)
- 線の太さ(0になっていないこと)
- 描画後の画像の扱い(表示や保存の処理に渡していること)
画像のスライシングで、縦横の順番はどうなっていますか
image_np[top:bottom, left:right]のとおり、縦(行)が先で、横(列)があとです。描画関数の座標指定は(x, y)の順なので、逆になります。
まとめ
OpenCVの描画関数を使えば、ポリゴン・矩形・直線を、短いコードで画像に描き込めます。押さえておきたいポイントは次の3つです。
- 座標の原点は左上、矩形は左上と右下の2点で指定
- 色の指定順はRGBではなくBGR
- 半透明にしたいときは、コピーへの描画と
cv2.addWeightedでの合成
物体検知や画像認識の結果を可視化する場面で、そのまま使える基本の組み合わせです。まずは矩形の枠線から試して、慣れてきたら半透明の塗りつぶしにも挑戦してみてください。
