LoginSignup
32

More than 3 years have passed since last update.

図形だけで初音ミクを描く【OpenCV】

Last updated at Posted at 2021-02-23

0. はじめに

  • OpenCVの図形を描画する関数を用いて,初音ミクを描きました. miku.png

※ この作品はピアプロ・キャラクター・ライセンスに基づいてクリプトン・フューチャー・メディア株式会社のキャラクター「初音ミク」を描いたものです

1. OpenCVの図形描画関数

  • こちらのサイトを参考にさせていただいております.

共通引数

  • img:図形を描画する画像
  • pt1:始点
  • pt2:終点
  • center:円の中心
  • color:色(今回はカラー画像で(Blue, Green, Red)で指定する)
  • thickness:線の太さ,長方形や円の場合は負の値を指定することで塗りつぶされる(デフォルト:1)
  • lineType:線のタイプ(デフォルト:8連結)
  • shift:座標と半径の値の小数点以下の桁を表すビット数(デフォルト:0)

図形を描画するための画像を用意する

# OpenCVのインポート
import cv2
# 画像をNumPyの配列ndarrayとして読み込んで操作するためにインポート
import numpy as np

# np.full((縦のサイズ, 横のサイズ, 3), (Blue, Green, Red), 
# 符号なし8ビット整数型)で図形を描画するための画像を作成
img = np.full((100, 100, 3), (211, 211, 211), dtype=np.uint8)

# image.pngという名前で出力する
cv2.imwrite('image.png', img)

image.png

線分:cv2.line()

# cv2.line(img, pt1, pt2, color, thickness, lineType, shift)
cv2.line(img, (10, 10), (90, 90), (0, 0, 255))
cv2.line(img, (90, 10), (10, 90), (0, 255, 0), thickness=3, lineType=cv2.LINE_AA)

image.png

矢印:cv2.arrowedLine()

# cv2.arrowedLine(img, pt1, pt2, color, thickness, lineType, shift, tipLength)
# tipLength:矢の先の部分の長さ,全体の長さに対する比で表す(デフォルト:0.1)
cv2.arrowedLine(img, (10, 10), (90, 90), (255, 0, 0), thickness=5)
cv2.arrowedLine(img, (90, 10), (10, 90), (0, 255, 255), tipLength=0.5)

image.png

長方形:cv2.rectangle()

# cv2.rectangle(img, pt1, pt2, color, thickness, lineType, shift)
cv2.rectangle(img, (10, 10), (90, 40), (255, 255, 0), thickness=-1)
cv2.rectangle(img, (10, 60), (90, 90), (255, 0, 255))

image.png

円:cv2.circle()

# cv2.circle(img, center, radius, color, thickness, lineType, shift)
# radius:円の半径
cv2.circle(img, (25, 25), 15, (0, 0, 255), thickness=-1)
cv2.circle(img, (65, 65), 20, (0, 255, 0), thickness=5, lineType=cv2.LINE_4)

image.png

楕円:cv2.ellipse()

# cv2.ellipse(img, box, color, thickness, lineType)
# box(center, axes, angle):centerは(x, y),axesは(横方向直径, 縦方向直径),
# angleはx軸方向を0度として時計回りに回転角度を指定する
cv2.ellipse(img, ((20, 35), (20, 50), 0), (255, 0, 0), thickness=-1)
cv2.ellipse(img, ((65, 65), (30, 60), 45), (0, 255, 255), thickness=2, lineType=cv2.LINE_AA)

image.png

円弧:cv2.ellipse()

# cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness, lineType, shift)
# axes:(横方向半径, 縦方向半径)
# startAngle:円弧の開始角度
# endAngle:終了角度
cv2.ellipse(img, (25, 50), (10, 25), 0, 0, 270, (255, 255, 0))
cv2.ellipse(img, (75, 50), (10, 25), 30, 0, 270, (255, 0, 255), thickness=-1)

image.png

2. 初音ミクを描く

背景

import cv2
import numpy as np

img = np.full((500, 500, 3), (211, 211, 211), dtype=np.uint8)

cv2.imwrite("miku.png", img)

miku.png

顔と眉

# 顔
cv2.circle(img, (250, 280), 150, (230, 240, 255), thickness=-1)

# 左眉
cv2.ellipse(img, (180, 270), (30, 10),   0, 210, 330, (0, 0, 0)) 
# 右眉
cv2.ellipse(img, (320, 270), (30, 10),   0, 210, 330, (0, 0, 0)) 

miku.png

# 髪土台
cv2.ellipse(img, (250, 175), ( 90, 150),  90,  90, 270, (118, 123,   1), thickness=-1)

# 前髪中央左
cv2.ellipse(img, (170, 270), (110, 110), 260,   0,  40, (118, 123,   1), thickness=-1)
# 前髪中央右
cv2.ellipse(img, (260, 160), ( 60, 150), 170, 270, 360, (118, 123,   1), thickness=-1)
# 前髪中央右調整
cv2.ellipse(img, (290, 180), ( 30, 140), 180, 270, 360, (230, 240, 255), thickness=-1)

# 触覚左
cv2.ellipse(img, (130, 160), ( 60, 220), 180, 270, 360, (118, 123,   1), thickness=-1)
# 触覚左調整
cv2.ellipse(img, (135, 200), ( 20, 180), 180, 270, 360, (230, 240, 255), thickness=-1)
# 前髪左
cv2.ellipse(img, (170, 180), ( 60, 120), 200, 270, 360, (118, 123,   1), thickness=-1)
# 触覚右
cv2.ellipse(img, (370, 160), ( 60, 220),   0,   0,  90, (118, 123,   1), thickness=-1)
# 触覚右調整
cv2.ellipse(img, (365, 200), ( 20, 180),   0,   0,  90, (230, 240, 255), thickness=-1)
# 前髪右
cv2.ellipse(img, (240, 175), (100, 220), 310,   0,  90, (118, 123,   1), thickness=-1)

# ツインテール左根元
cv2.ellipse(img, (150, 200), (100, 100), 120,   0, 180, (118, 123,   1), thickness=-1)
# ツインテール左毛先
cv2.ellipse(img, ( 80, 340), (180,  80), 100,   0, 180, (118, 123,   1), thickness=-1)
# ツインテール右根元
cv2.ellipse(img, (350, 200), (100, 100), 120,  90, 270, (118, 123,   1), thickness=-1)
# ツインテール右毛先
cv2.ellipse(img, (420, 340), (180,  80), 260,   0, 180, (118, 123,   1), thickness=-1)

# 触覚左影
cv2.ellipse(img, (105, 260), ( 20,  90),   0,  90, 270, ( 53,  53,  23), thickness=-1)
# ツインテール左影
cv2.ellipse(img, ( 80, 340), (120,  20), 100,   0, 180, ( 53,  53,  23), thickness=-1)
# 触覚左影調整
cv2.ellipse(img, (110, 260), ( 20,  90),   0,  90, 270, (118, 123,   1), thickness=-1)
# 触覚右影
cv2.ellipse(img, (395, 260), ( 20,  90), 180,  90, 270, ( 53,  53,  23), thickness=-1)
# ツインテール右影
cv2.ellipse(img, (420, 340), (120,  20), 260,   0, 180, ( 53,  53,  23), thickness=-1)
# 触覚右影調整
cv2.ellipse(img, (390, 260), ( 20,  90), 180,  90, 270, (118, 123,   1), thickness=-1)

miku.png

口と頬

# 口
cv2.ellipse(img, (250, 350), ( 15,  15),   0,  40, 140, (  0,   0,   0)) 

# 左頬
cv2.ellipse(img, ((160, 350), ( 60,  80), -25), (225, 225, 250), thickness=-1)
# 右頬
cv2.ellipse(img, ((340, 350), ( 60,  80),  25), (225, 225, 250), thickness=-1)

miku.png

# 左目白
cv2.ellipse(img, ((180, 320), ( 60,  80),   0), (255, 255, 255), thickness=-1)
# 左目緑大
cv2.ellipse(img, ((185, 315), ( 48,  70),   0), (118, 123,   1), thickness=-1)
# 左目緑中
cv2.ellipse(img, ((190, 326), ( 40,  50),   0), (176, 162, 101), thickness=-1)
# 左目緑小
cv2.ellipse(img, ((193, 330), ( 30,  40),   0), (168, 184, 103), thickness=-1)
# 左目緑中央
cv2.ellipse(img, ((190, 310), ( 15,  20),   0), (118, 118,  73), thickness=-1)  
# 左目灰
cv2.ellipse(img, ((195, 340), ( 20,  20),   0), (169, 169, 169), thickness=-1)
# 左目光大
cv2.ellipse(img, ((190, 290), ( 10,  15), -20), (255, 255, 255), thickness=-1)
# 左目光小
cv2.ellipse(img, ((185, 290), ( 10,   5),   0), (255, 255, 255), thickness=-1)

# 左目線上
cv2.ellipse(img, (175, 300), ( 20,  40),  90, 120, 240, (  0,   0,   0), thickness=5) 
# 左目線下
cv2.ellipse(img, (175, 300), ( 20,  40),  60, 120, 190, (  0,   0,   0), thickness=5) 

# 右目白
cv2.ellipse(img, ((320, 320), ( 60,  80),   0), (255, 255, 255), thickness=-1)
# 右目緑大
cv2.ellipse(img, ((315, 315), ( 48,  70),   0), (118, 123,   1), thickness=-1)
# 右目緑中
cv2.ellipse(img, ((310, 326), ( 40,  50),   0), (176, 162, 101), thickness=-1)
# 右目緑小
cv2.ellipse(img, ((307, 330), ( 30,  40),   0), (168, 184, 103), thickness=-1)
# 右目緑中央
cv2.ellipse(img, ((310, 310), ( 15,  20),   0), (118, 118,  73), thickness=-1)
# 右目灰
cv2.ellipse(img, ((305, 340), ( 20,  20),   0), (169, 169, 169), thickness=-1)
# 右目光大
cv2.ellipse(img, ((320, 290), ( 10,  15), -20), (255, 255, 255), thickness=-1)
# 右目光小
cv2.ellipse(img, ((315, 290), ( 10,   5),   0), (255, 255, 255), thickness=-1)

# 右目線上
cv2.ellipse(img, (325, 300), ( 20,  40),  90, 120, 240, (  0,   0,   0), thickness=5) 
# 右目線下
cv2.ellipse(img, (325, 300), ( 20,  40), 120, 170, 250, (  0,   0,   0), thickness=5) 

miku.png

髪飾り

# 左髪飾り左上黒
cv2.line(img, (150, 110), (110, 150), (  0,   0,   0), thickness=30)
# 左髪飾り左下黒
cv2.line(img, (130, 170), (110, 150), (  0,   0,   0), thickness=30)
# 左髪飾り左上赤
cv2.line(img, (150, 110), (110, 150), (  0,   0, 255), thickness=10)
# 左髪飾り左下赤
cv2.line(img, (130, 170), (110, 150), (  0,   0, 255), thickness=10)

# 右髪飾り左上黒
cv2.line(img, (370, 110), (330, 150), (  0,   0,   0), thickness=30)
# 右髪飾り左下黒
cv2.line(img, (370, 190), (330, 150), (  0,   0,   0), thickness=30)
# 右髪飾り右下黒
cv2.line(img, (370, 190), (390, 170), (  0,   0,   0), thickness=30)
# 右髪飾り左上赤
cv2.line(img, (370, 110), (330, 150), (  0,   0, 255), thickness=10)
# 右髪飾り左下赤
cv2.line(img, (370, 190), (330, 150), (  0,   0, 255), thickness=10)
# 右髪飾り右下赤
cv2.line(img, (370, 190), (390, 170), (  0,   0, 255), thickness=10)

miku.png

3. 髪型と目の色を変えると

  • 別のキャラクターになります
import cv2
import numpy as np

img = np.full((500, 500, 3), (211, 211, 211), dtype=np.uint8)

# 左髪後ろ中央
cv2.ellipse(img, (160, 300), ( 30, 120),  20,   0,  90, ( 50, 148, 188), thickness=-1)
# 左髪後ろ左
cv2.ellipse(img, (160, 300), ( 30, 120),  40,   0,  90, ( 50, 148, 188), thickness=-1)
# 左髪後ろ右
cv2.ellipse(img, (150, 310), ( 30, 120),   0,   0,  90, ( 50, 148, 188), thickness=-1)

# 右髪後ろ中央
cv2.ellipse(img, (340, 300), ( 30, 120), 340,  90, 180, ( 50, 148, 188), thickness=-1)
# 右髪後ろ右
cv2.ellipse(img, (340, 300), ( 30, 120), 320,  90, 180, ( 50, 148, 188), thickness=-1)
# 右髪後ろ左
cv2.ellipse(img, (350, 310), ( 30, 120),   0,  90, 180, ( 50, 148, 188), thickness=-1)

# 顔
cv2.circle(img, (250, 280), 150, (230, 240, 255), thickness=-1)

# 左眉
cv2.ellipse(img, (180, 270), (30, 10),   0, 210, 330, (0, 0, 0)) 
# 右眉
cv2.ellipse(img, (320, 270), (30, 10),   0, 210, 330, (0, 0, 0)) 

# 髪土台
cv2.ellipse(img, (250, 175), ( 90, 150),  90,  90, 270, (  0, 186, 255), thickness=-1)
# 前髪中央左
cv2.ellipse(img, (240, 250), (110, 110), 220,   0,  40, (  0, 186, 255), thickness=-1)
# 前髪中央右
cv2.ellipse(img, (260, 130), ( 60, 150), 140, 270, 360, (  0, 186, 255), thickness=-1)
# 前髪中央右調整
cv2.ellipse(img, (300, 170), ( 30, 140), 140, 270, 360, (230, 240, 255), thickness=-1)

# 触覚左
cv2.ellipse(img, (130, 260), ( 60, 130),   0,  90, 270, (  0, 186, 255), thickness=-1)
# 触覚左調整
cv2.ellipse(img, (135, 200), ( 20, 180), 180, 270, 360, (230, 240, 255), thickness=-1)
# 前髪左
cv2.ellipse(img, (170, 180), ( 60, 120), 200, 270, 360, (  0, 186, 255), thickness=-1)
# 触覚左影
cv2.ellipse(img, (125, 300), ( 10,  70),   0,  90, 270, ( 36, 106, 134), thickness=-1)
# 触覚右
cv2.ellipse(img, (370, 260), ( 60, 130),   0, 270, 450, (  0, 186, 255), thickness=-1)
# 触覚右調整
cv2.ellipse(img, (365, 200), ( 20, 180),   0,   0,  90, (230, 240, 255), thickness=-1)
# 前髪右
cv2.ellipse(img, (370, 155), ( 20, 220),   0,   0,  90, (  0, 186, 255), thickness=-1)
cv2.ellipse(img, (310, 210), ( 70,  70),  35, 180, 360, (  0, 186, 255), thickness=-1)
# 触覚右影
cv2.ellipse(img, (375, 300), ( 10,  70), 180,  90, 270, ( 36, 106, 134), thickness=-1)

# 口
cv2.ellipse(img, (250, 350), ( 15,  15),   0,  40, 140, (  0,   0,   0)) 

# 左頬
cv2.ellipse(img, ((160, 350), ( 60,  80), -25), (225, 225, 250), thickness=-1)
# 右頬
cv2.ellipse(img, ((340, 350), ( 60,  80),  25), (225, 225, 250), thickness=-1)

# 左目白
cv2.ellipse(img, ((180, 320), ( 60,  80),   0), (255, 255, 255), thickness=-1)
# 左目青大
cv2.ellipse(img, ((185, 315), ( 48,  70),   0), (128,  77,   0), thickness=-1)
# 左目青中
cv2.ellipse(img, ((190, 326), ( 40,  50),   0), (186, 118,   0), thickness=-1)
# 左目青小
cv2.ellipse(img, ((193, 330), ( 30,  40),   0), (255, 162,   0), thickness=-1)
# 左目青中央
cv2.ellipse(img, ((190, 310), ( 15,  20),   0), (147,  84,   0), thickness=-1)  
# 左目灰
cv2.ellipse(img, ((195, 340), ( 20,  20),   0), (169, 169, 169), thickness=-1)
# 左目光大
cv2.ellipse(img, ((190, 290), ( 10,  15), -20), (255, 255, 255), thickness=-1)
# 左目光小
cv2.ellipse(img, ((185, 290), ( 10,   5),   0), (255, 255, 255), thickness=-1)

# 左目線上
cv2.ellipse(img, (175, 300), ( 20,  40),  90, 120, 240, (  0,   0,   0), thickness=5) 
# 左目線下
cv2.ellipse(img, (175, 300), ( 20,  40),  60, 120, 190, (  0,   0,   0), thickness=5) 

# 右目白
cv2.ellipse(img, ((320, 320), (  60,  80),   0), (255, 255, 255), thickness=-1)
# 右目青大
cv2.ellipse(img, ((315, 315), (  48,  70),   0), (128,  77,   0), thickness=-1)
# 右目青中
cv2.ellipse(img, ((310, 326), (  40,  50),   0), (186, 118,   0), thickness=-1)
# 右目青小
cv2.ellipse(img, ((307, 330), (  30,  40),   0), (255, 162,   0), thickness=-1) 
# 右目青中央
cv2.ellipse(img, ((310, 310), (  15,  20),   0), (147,  84,   0), thickness=-1)
# 右目灰
cv2.ellipse(img, ((305, 340), (  20,  20),   0), (169, 169, 169), thickness=-1)
# 右目光大
cv2.ellipse(img, ((320, 290), (  10,  15), -20), (255, 255, 255), thickness=-1)
# 右目光小
cv2.ellipse(img, ((315, 290), (  10,   5),   0), (255, 255, 255), thickness=-1)   

# 右目線上
cv2.ellipse(img, (325, 300), ( 20,  40),  90, 120, 240, (  0,   0,   0), thickness=5)
# 右目線下
cv2.ellipse(img, (325, 300), ( 20,  40), 120, 170, 250, (  0,   0,   0), thickness=5) 

# ピン留め左上
cv2.line(img, (150, 230), (140, 220), (255, 255, 255), thickness=10)
# ピン留め左下
cv2.line(img, (140, 245), (130, 235), (255, 255, 255), thickness=10)
# ピン留め右上
cv2.line(img, (260, 210), (270, 200), (255, 255, 255), thickness=10)
# ピン留め右下
cv2.line(img, (300, 240), (310, 230), (255, 255, 255), thickness=10)

# カチューシャ左
cv2.ellipse(img, (145,  160), ( 100,  20),  315,   180,  360, (255, 255, 255), thickness=-1) 
# カチューシャ右
cv2.ellipse(img, (355,  160), ( 100,  20),   45,   180,  360, (255, 255, 255), thickness=-1) 
# リボン左内側
cv2.circle(img, (190, 100),  50, (255, 255, 255), thickness=-1)
# リボン左外側
cv2.ellipse(img, (160,  60), ( 80,  70),  90,   0,  90, (255, 255, 255), thickness=-1) 
# リボン右内側
cv2.circle(img, (310, 100),  50, (255, 255, 255), thickness=-1)
# リボン右外側
cv2.ellipse(img, (340,  60), ( 80,  70), 180, 180, 270, (255, 255, 255), thickness=-1) 
# リボン中心
cv2.rectangle(img, (240,  80), (260, 120), (255, 255, 255), thickness=-1)

cv2.imwrite("rin.png", img)

rin.png

※ この作品はピアプロ・キャラクター・ライセンスに基づいてクリプトン・フューチャー・メディア株式会社のキャラクター「鏡音リン」を描いたものです

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32