😸

OpenCVの文字描画とサイズ指定

2023/01/21に公開

基本

OpenCVの描画系関数の仕様はここ。文字を描画するときはcv::putText()を使う。

  • img: 描画対象の画像
  • text: 文字列
  • org: 描画位置
  • fontFace: フォントの種類
  • fontScale: フォントのサイズ
  • color: 色
  • thickness: 太さ
  • lineType: 線の種類
  • bottomLeftOrigin: 画像の原点が左下か。Falseなら左上

Pythonでの使い方はこんな感じ。

# 縦300, 横100, 3チャンネル画像バッファを作る
img = np.zeros((100, 300, 3), dtype=np.uint8)
# x: 20, y: 50の位置に"ABCxyz"という文字列を描画
cv2.putText(img, "ABCxyz", (20, 50), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255,255,255))
# 座標が分かるよう線を引く
cv2.line(img, (20,50), (300,50), (0,0,255))
cv2.imshow("Test", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

位置とサイズに関する詳細

上の結果で分かるとおり、orgで指定するのは文字描画領域の左下だが、小文字のj, q, yなどはorgのy座標よりも下にはみ出る。なおorgのy座標の位置の線をベースラインと呼ぶ。

フォントサイズは引数fontScaleで指定するが、ピクセル数などの分かりやすい単位ではない。各フォントには固有の基本サイズがあり、それに対して何倍かを指定する。

これだと使いにくいので、フォントの高さからscaleを取得する関数がある。HERSHEY_DUPLEXフォントで高さを20ピクセルにしたければ、以下のようにscaleは0.905くらいになる。

face = cv2.FONT_HERSHEY_DUPLEX
scale = cv2.getFontScaleFromHeight(face, 20)
print(scale) #=> 0.9047619047619048

描画する文字列・フォント種類・スケール・太さが決まれば、cv2.getTextSize()関数で実際に描画したときのサイズ(幅・高さ)と、ベースラインから下に出る部分の長さを取得することができる。

例えば以下の設定で描いた場合、描画領域(ただしベースラインより上の部分)は幅198, 高さ30ピクセルになり、ベースラインより下は17ピクセルになる。

text = "ABCxyz"
face = cv2.FONT_HERSHEY_PLAIN
height = 30
thickness = 1

scale = cv2.getFontScaleFromHeight(face, height, thickness)
size, baseline = cv2.getTextSize(text, face, scale, thickness)
print(f"{size} {baseline}") #=> (198, 30) 17

関数仕様

cv2.getFontScaleFromHeight

cv2.getTextSize

応用: 半透明の矩形で囲まれた文字を描画

写真に注釈を入れるとき、文字をそのまま描くと背景と色が近い場合に見にくくなってしまう。そういう場合は半透明の矩形を描くとよい。OpenCVの図形描画機能では透明度の指定ができないため、cv2:addWeighted()を使って合成している。矩形の大きさは上のやり方で決める。

# 半透明な矩形を描く
def draw_translucent_rect(img, x, y, w, h):
    sub_img = img[y:y+h, x:x+w]
    black_rect = np.zeros(sub_img.shape, dtype=np.uint8)
    rect = cv2.addWeighted(sub_img, 0.3, black_rect, 0.7, 1.0)
    img[y:y+h, x:x+w] = rect

# 半透明な矩形の上に文字列を描く
# 高さ (height) はピクセル数で指定する
# paddingで余白部分のサイズを指定する
def draw_text_with_box(img, text, org, padding, face, height, color, thickness=1, lineType=cv2.LINE_8):
    scale = cv2.getFontScaleFromHeight(face, height, thickness)
    size, baseline = cv2.getTextSize(text, face, scale, thickness)
    rect_x = org[0] - padding
    rect_y = org[1] - height - padding
    rect_w = size[0] + (padding*2)
    rect_h = size[1] + baseline + (padding*2)
    rect_x = max(rect_x, 0)
    rect_y = max(rect_y, 0)
    max_w = img.shape[1] - rect_x
    max_h = img.shape[0] - rect_y
    rect_w = min(rect_w, max_w)
    rect_h = min(rect_h, max_h)
    draw_translucent_rect(img, rect_x, rect_y, rect_w, rect_h)
    cv2.putText(img, text, org, face, scale, color, thickness)

img = cv2.imread("sample0512.jpg")
draw_text_with_box(img, "Hello", (30,100), 0, cv2.FONT_HERSHEY_COMPLEX, 20, (255,255,255), 1, cv2.LINE_AA)
draw_text_with_box(img, "abcxyz", (400,220), 5, cv2.FONT_HERSHEY_COMPLEX, 30, (255,255,255), 1, cv2.LINE_AA)
cv2.imshow("Test", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Discussion