【Python】OpenCVで図形の描画からアニメーションまで【線・四角・丸・塗りつぶし】

はじめに
この記事では、PythonでOpenCVを使って図形を描画する方法をご紹介します。線をはじめ、四角や丸、多角形、塗りつぶしといったことから、図形を動かすアニメーションのやり方までお伝えしていきます。記事の最後ではProcessingでやるようなジェネラティブアートにも挑戦してみましたので、最後までお楽しみください。
準備
ここではPython3.xでOpenCVを扱います。
$ pip list | grep opencv
opencv-contrib-python 4.6.0.66
opencv-python 4.5.5.62
なければpipでOpenCVをインストールしてください。numpyも使います。
$ pip install opencv-python
$ pip install opencv-contrib-python
$ pip install numpy
OpenCVで色々な図形を描画
ラインを引く
OpenCVでラインを引く例です:
import cv2
import numpy as np
= np.zeros((300, 300, 3), dtype="uint8")
canvas
= (0, 255, 128)
color 0, 0), (300, 300), color)
cv2.line(canvas, (300, 0), (0, 300), color, thickness=5)
cv2.line(canvas, (0, 150), (300, 150), color, thickness=1, lineType=cv2.LINE_8)
cv2.line(canvas, (
"Canvas", canvas)
cv2.imshow('../canvas_line.jpg', canvas)
cv2.imwrite(
0) # Press Esc to close window cv2.waitKey(
実行結果が次になります。
図形を描く(四角、丸、楕円)
OpenCVで四角、丸、楕円などの図形を描く例です:
import cv2
import numpy as np
= np.zeros((300, 300, 3), dtype="uint8")
canvas
= (0, 255, 128)
color
30,20), (100,80), color, 1)
cv2.rectangle(canvas, (170,20), (200,100), color, -1) # Set -1 or cv.FILLED to thickness is fill
cv2.rectangle(canvas, (
100,200), 50, color, 1)
cv2.circle(canvas,(
220,220), (80,30), 45, 0, 330, 255, cv2.FILLED)
cv2.ellipse(canvas, (
"Canvas", canvas)
cv2.imshow('../canvas_shape.jpg', canvas)
cv2.imwrite(
0) # Press Esc to close window cv2.waitKey(
実行結果が次になります。
ポリゴン(多角形)
OpenCVで多角形を描く例です:
import cv2
import numpy as np
= np.zeros((300, 300, 3), dtype="uint8")
canvas
= (0, 255, 128)
color
= np.array([[10,10],[50,10],[100,50],[50,200],[10,180]], np.int32) # vertices
pts1 True, color, 1) # thickness=-1 does not work (use cv2.fillPoly)
cv2.polylines(canvas, [pts1],
= np.array([[150,10],[200,10],[220,80],[150,130]], np.int32)
pts2 255)
cv2.fillPoly(canvas, [pts2],
= np.array([[150,150],[180,170],[220,250],[290,290]], np.int32)
pts3 False, color, 1)
cv2.polylines(canvas, [pts3],
"Canvas", canvas)
cv2.imshow('../canvas_polygon.jpg', canvas)
cv2.imwrite(
0) # Press Esc to close window cv2.waitKey(
実行結果が次になります。
ポリゴンを塗りつぶしたい場合は cv2.polylines
ではなく cv2.fillPoly
を使います。
テキストの描画
OpenCVでテキストを描く例です:
import cv2
import numpy as np
from PIL import Image,ImageFont,ImageDraw
= np.zeros((300, 300, 3), dtype="uint8")
canvas
= (255, 255, 255)
color
= cv2.FONT_HERSHEY_SIMPLEX
font_en 'Hello world', (30, 60), font_en, 1, color, 1, cv2.LINE_AA)
cv2.putText(canvas,
"""
Draw Japanese text with PIL
"""
= ImageFont.truetype("/Users/mopipico/Library/Fonts/yawarakadragon.otf", 25)
font_ja
= Image.fromarray(canvas[:, :, ::-1]) # OpenCV to PIL
img = ImageDraw.Draw(img)
draw
30, 150), 'こんにちは、世界!', font=font_ja, fill=color)
draw.text((
= np.array(np.array(img, dtype="uint8"))[:, :, ::-1] # PIL to OpenCV
canvas
"Canvas", canvas)
cv2.imshow('../canvas_text.jpg', canvas)
cv2.imwrite(
0) # Press Esc to close window cv2.waitKey(
実行結果が次になります。
cv2.putText
では、日本語文字を表示できないので、PILを使って描画しました。ただし、OpenCVのcanvas配列とPILのImageオブジェクトの整合性がとれるように配列を変換する必要があります。
OpenCVでアニメーション(図形を動かしてみよう)
Processingのように図形を動かしてみたい方もいらっしゃるのではないでしょうか?PythonのOpenCVでも図形アニメーションは可能です。
ボールを画面内で跳ね返すアニメーション
OpenCV図形を動かす例です:
import cv2
import numpy as np
def fill(size, color):
= size
w, h = np.zeros((h, w, 3), dtype="uint8")
canvas 0,0), (w,h), color, -1)
cv2.rectangle(canvas, (return canvas
def main(framesize, frame_max, writer):
# -------------------------------------------------
# setup
# -------------------------------------------------
= framesize
width, height
= 0
frame_counter
= (150, 130)
last_pos = 6
dx = 12
dy = (0, 255, 128)
color = (128, 128, 128)
bg_color
= 30
radius
# -------------------------------------------------
# loop
# -------------------------------------------------
while True:
if frame_counter > frame_max:
break
= fill(framesize, bg_color)
canvas
= last_pos[0]
x = last_pos[1]
y
if (x+radius/2 > width) or (x-radius/2 < 0):
= -1 * dx
dx if (y+radius/2 > height) or (y-radius/2 < 0):
= -1 * dy
dy
= (x+dx, y+dy)
pt = (pt)
last_pos -1)
cv2.circle(canvas, pt, radius, color,
writer.write(canvas)"Canvas", canvas)
cv2.imshow(
= cv2.waitKey(5)
key if key == 27: # Esc
break
+= 1
frame_counter
if __name__ == "__main__":
= '../build/canvas_animation.mp4'
outpath
= (300, 300)
framesize = 30
fps = 4 * fps # 5sec
frame_max
= cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fmt = cv2.VideoWriter(outpath, fmt, fps, framesize) # ライター作成
writer
main(framesize, frame_max, writer) writer.release()
実行結果が次になります。

Processingの setup
や loop
と同じように考えれば何もむつかしくないです。while文(loop)内でやっていることは、これまでに紹介してきた図形の描画と同じプログラミングです。fill()
で背景色付きのcanvasを作って、図形を配置するだけです。その図形の位置を少しずつ動かしていけばアニメーションの完成です。
上記のプログラム例では、canvas領域のはみ出し判定も行なってます。これを発展させれば、図形同士の当たり判定も同じようにしてできるはずです。
ジェネラティブアートっぽいこと
Processingなどのジェネラティブアートでよく見かける「点と点を線で結ぶ」アニメーションの例です:
import cv2
import numpy as np
from numpy.random import *
import itertools
def fill(size, color):
= size
w, h = np.zeros((h, w, 3), dtype="uint8")
canvas 0,0), (w,h), color, -1)
cv2.rectangle(canvas, (return canvas
def is_nearby(pt1, pt2):
if (pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2 < 6000:
return True
return False
def main(framesize, frame_max, writer):
# -------------------------------------------------
# setup
# -------------------------------------------------
= framesize
width, height
= 0
frame_counter
= 2
radius = 64 # ball len
num = 3
max_speed = [(randint(width), randint(height)) for _ in range(num)]
last_pts = np.array([(randint(max_speed * 2), randint(max_speed * 2)) for _ in range(num)]) - 3 # dx, dy
dpos
= (222, 222, 222)
color = (0, 0, 0)
bg_color
# -------------------------------------------------
# loop
# -------------------------------------------------
while True:
if frame_counter > frame_max:
break
= fill(framesize, bg_color)
canvas
for i, pt in enumerate(last_pts):
= dpos[i]
dx, dy
= pt[0] + dx
x = pt[1] + dy
y
if x>width or x<0:
*= -1
dx if y>height or y<0:
*= -1
dy
-1)
cv2.circle(canvas, pt, radius, color, = [x, y]
last_pts[i] = [dx, dy]
dpos[i]
for pair in itertools.combinations(last_pts, 2): # combination
= pair
pt1, pt2 if is_nearby(pt1, pt2):
cv2.line(canvas, pt1, pt2, color)
writer.write(canvas)"Canvas", canvas)
cv2.imshow(if frame_counter == 0:
'../dots_and_lines.png', canvas)
cv2.imwrite(
= cv2.waitKey(5)
key if key == 27: # Esc
break
+= 1
frame_counter
if __name__ == "__main__":
= '../build/canvas_animation2.mp4'
outpath
= (600, 400)
framesize = framesize
w, h = 30
fps = 10 * fps # 5sec
frame_max
= cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fmt = cv2.VideoWriter(outpath, fmt, fps, framesize) # ライター作成
writer
main(framesize, frame_max, writer) writer.release()
実行結果が次になります。

複雑そうに見えますが、それほど難しいプログラミングではありません。ポイントは次のとおりです。
- ランダムで点をcanvas上に配置
- それぞれの点の移動方向速度を用意します。
- 2つの点の組み合わせを
itertools.combinations
で作成、 - 点の点の距離を計算します。
- 近ければ点と点を線で結ぶ
関連記事
- 【Python】OpenCVで特徴点の追跡【メダカの軌跡】
- 【Python】OpenCVでコーナーの検出【Harris/Shi-Tomasi】
- 【Python】OpenCVで画像操作いろいろ(グレースケール・モノ・輪郭抽出・切り抜く・透過)
- 【Python】VidStabで手ぶれ補正【動画編集への道#2】
- 【断酒iOSアプリ制作】紙吹雪アニメーション (15日目)