注意(2023.06.11)
この記事は使えなくなります。
最新記事「OpenCVで日本語フォントを描画する を関数化する を最新にする」をご利用ください。
はじめに
多くのLGTMありがとうございます。この話には続きがありますので良かったらそちらもご覧ください。
OpenCVで日本語フォントを描画する を関数化する を汎用的にする
で。前回の続きです。
OpenCVでテキストを描画する関数 cv2.putText()
は日本語を使えない。PILならば可能なのでその部分だけPILを使うという手法がウェブ上で多数見られるが、とりあえずやってみたという内容ばかりで cv2.putText()
の代替となるような独自関数を公開しているところはなかった。
そこで自分で作ってみた。私の検索スキルが低いだけで定番となっている方法があるのでしたらごめんなさい。
バージョン:
OpenCV 4.1.2.30
Pillow 7.0
復習
OpenCVで文字を描く cv2.putText(img, text, org, fontFace, fontScale, color, thickness)
- img 画像。言うまでもなく、OpenCVの画像。
- text テキスト。日本語不可。改行コードで改行させることはできない。
-
org テキストの左下の座標を
(x, y)
で指定する。 -
fontFace フォント。
cv2.FONT_HERSHEY_SIMPLEX
など数種類のみ指定可能。 - fontScale フォントのサイズ。面倒なことに必須。「1」の大きさはフォントにより異なる。
-
color テキストの色。言うまでもなく、RGB画像ならばRGBでなく
(b, g, r)
の順。 - thickness 線の太さ。省略可能でデフォ値は1。
PILで文字を描く ImageDraw.text(xy, text, fill, font)
対象はPIL画像から作成したImageDraw.Drawオブジェクト。
-
xy テキストの左上の座標を
(x,y)
で指定する。 -
text テキスト。日本語OK。
\n
を使っての改行もOK。 -
fill テキストの色。言うまでもなく、RGB画像ならば
(r, g, b)
。 - font フォント。ImageFont.truetypeでフォントとサイズを指定する。
ということは
cv2.putText()
にある thickness は、ベクターフォントだからこそ意味がある引数。PILになくても問題ない。
それ以外は cv2.putText()
を ImageDraw.text()
に置き換えることができそうだ。
関数化する
cv2.imshow()
が使えない Google Colab では、それに相当する独自関数 cv2_imshow() が用意されている。
それにならって、OpenCVで日本語フォントを描画する独自関数を cv2_putText() と名付ける。
まず二つの関数を用意しておく。こちらの記事で書いた、OpenCVとPILを変換する関数だ。
import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
def pil2cv(imgPIL):
imgCV_RGB = np.array(imgPIL, dtype = np.uint8)
imgCV_BGR = np.array(imgPIL)[:, :, ::-1]
return imgCV_BGR
def cv2pil(imgCV):
imgCV_RGB = imgCV[:, :, ::-1]
imgPIL = Image.fromarray(imgCV_RGB)
return imgPIL
その1
def cv2_putText_1(img, text, org, fontFace, fontScale, color):
x, y = org
b, g, r = color
colorRGB = (r, g, b)
imgPIL = cv2pil(img)
draw = ImageDraw.Draw(imgPIL)
fontPIL = ImageFont.truetype(font = fontFace, size = fontScale)
draw.text(xy = (x,y), text = text, fill = colorRGB, font = fontPIL)
"""
後でここに追加する
"""
imgCV = pil2cv(imgPIL)
return imgCV
いったんPIL画像にして、それに対して日本語テキストを描画し、再度OpenCV画像に戻すというのは多くの先行者と同じだが、cv2.putText(img, text, org, fontFace, fontScale, color, thickness)
と同じ使い方ができるのが特徴。
もちろん「日本語は使いたいけどフォントやサイズは固定なのよね」という場合はデフォルト引数を設定してもよい。
cv2.putText()
とcv2_putText()
の出力結果を比較してみよう。
import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
def pil2cv(imgPIL):
# 上に挙げたやつ
def cv2pil(imgCV):
# 上に挙げたやつ
def cv2_putText_1(img, text, org, fontFace, fontScale, color):
# 上に挙げたやつ
def main():
img = np.full((200,400,3), (160,160,160), dtype=np.uint8)
# 普通にcv2.putText()でテキストを描画する
text = "OpenCV"
x, y = 50, 100
fontCV = cv2.FONT_HERSHEY_SIMPLEX
fontScale = 1
colorBGR = (255,0,0)
thickness = 1
cv2.putText(img = img,
text = text,
org = (x,y),
fontFace = fontCV,
fontScale = fontScale,
color = colorBGR,
thickness = thickness)
"""
後でここに追加する
"""
# 独自関数で日本語テキストを描画する
text = "日本語も\n可能なり"
x, y = 200,100
fontPIL = "Dflgs9.TTC" # DF麗雅宋
size = 40
colorBGR = (255,0,0) # cv2.putText()と同じく、BGRの順で定義
img = cv2_putText_1(img = img,
text = text,
org = (x,y),
fontFace = fontPIL,
fontScale = size,
color = colorBGR)
cv2.imshow("cv2_japanese_test", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
y座標を同じ値にしたはずなのだが、高さがずれている。復習の項で書いたように cv2.putText()
の org は左下を指定し ImageDraw.text()
の xy は右上を指定するという違いがあるからだ。
それがわかるような加工をしてみよう。
# cv2_putText_1() のコメント行に以下を追加する
w, h = draw.textsize(text, font = fontPIL)
draw.rectangle([(x,y), (x+w,y+h)], outline = (255,0,0), width = 1)
draw.ellipse([(x-3,y-3), (x+3,y+3)], None, (255,0,0), 1)
# main() のコメント行に以下を追加する
(w, h), b = cv2.getTextSize(text = text,
fontFace = fontCV,
fontScale = fontScale,
thickness = thickness)
pt1 = (x, y - h)
pt2 = (x + w, y + b)
cv2.rectangle(img, pt1, pt2, (0,0,255), 1)
cv2.circle(img, (x,y), 5, (0,0,255))
前の記事では cv2.getTextSize()
で得られるベースラインを使わなかったが、今回はそれも含めて四角形を描いてみた。
PILの四角形が日本語テキストを微妙に収めきれていない理由はよくわからない。もしかしたらこちらの方が言及しているバグが関係しているのかもしれない。
とにかく、独自関数を cv2.putText()
の org と互換性を持たせるには、この部分を修正する必要がある。
その2
高さの基準を修正したものがこちら。
def cv2_putText_2(img, text, org, fontFace, fontScale, color):
x, y = org
b, g, r = color
colorRGB = (r, g, b)
imgPIL = cv2pil(img)
draw = ImageDraw.Draw(imgPIL)
fontPIL = ImageFont.truetype(font = fontFace, size = fontScale)
w, h = draw.textsize(text, font = fontPIL)
draw.text(xy = (x,y-h), text = text, fill = colorRGB, font = fontPIL)
imgCV = pil2cv(imgPIL)
return imgCV
main()
の中にある cv2_putText_1
を cv2_putText_2
に変更すればこのような画像が得られる。
cv2.putText()
と同様に、左下を指定してテキストを描画することができた。
もっとも、私は cv2.putText()
で org が左下を指定する仕様なのがそもそも気に入らないので cv2_putText_1()
のほうが好みなのだが。
その3
cv2.putText()
でフォントとフォントサイズを指定すれば描画されるテキストのサイズが決まる。それを元に指定したフォントでの適切なサイズを算出すれば、こちらにあるような既存のプログラムを最小限の改造で任意のTrueTypeフォントに変更することができる! と思ったけど、面倒なのでやめた。。
終わりに
関数化しておけば、cv2.putText()
と同等の使い方で日本語フォントを使うことができる。というお話でした。
ただし、当然、cv2.imshow()
のウィンドウ名や cv2.imwrite()
のファイル名で日本語が使えるようになったわけではないので注意。
ところで、(r, g, b)
というタプルから(b, g, r)
を作り出すにはどうしたらよいのだろう。
今回の我が関数の中では各要素を取り出して順番を変えた上で新しいタプルを作っているが、エレガントな解法とは言えない。
タプルをリストに変換し、順序を反転してからまたタプルに変換するという方法もあるが、これもエレガントにはほど遠い方法だ。
r, g, b = 0, 128, 255
RGB = (r, g, b)
BGR = tuple(list(RGB)[::-1])
print (RGB) # (0, 128, 255)
print (BGR) # (255, 128, 0)
RGB画像をBGRに変換する際にimg[:, :, ::-1]
とするような、アハ体験するほどのナイスな方法がありましたらぜひ教えて下さい。
多くのLGTMありがとうございます。この話には続きがありますので良かったらそちらもご覧ください。
OpenCVで日本語フォントを描画する を関数化する を汎用的にする