【廃止】OpenCVで日本語フォントを描画する　を関数化する

# 注意（2023.06.11）
この記事は使えなくなります。
最新記事「OpenCVで日本語フォントを描画する　を関数化する　を最新にする」をご利用ください。

https://qiita.com/mo256man/items/f07bffcf1cfedf0e42e0

# はじめに
多くのLGTMありがとうございます。この話には続きがありますので良かったらそちらもご覧ください。
　　[OpenCVで日本語フォントを描画する　を関数化する　を汎用的にする](https://qiita.com/mo256man/items/b6e17b5a66d1ea13b5e3)

で。[前回](https://qiita.com/mo256man/items/f104b1d03821d64b403d)の続きです。

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()** と名付ける。

まず二つの関数を用意しておく。[こちらの記事](https://qiita.com/mo256man/items/a2bfdf2393953314fa39)で書いた、OpenCVとPILを変換する関数だ。

```Python:
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

```Python:
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()`の出力結果を比較してみよう。

```Python:
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()
```

結果はこう。
![cv_jp_font_test_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/72c73506-d577-45dd-9a08-2f3e9ccd0823.png)


y座標を同じ値にしたはずなのだが、高さがずれている。復習の項で書いたように `cv2.putText()` の *org* は左下を指定し `ImageDraw.text()` の *xy* は右上を指定するという違いがあるからだ。
それがわかるような加工をしてみよう。

```Python:
# 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))

```

結果はこう。
![cv_jp_font_test_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/1e06eaa6-5b35-adc7-cdc7-432741894e62.png)

前の記事では `cv2.getTextSize()` で得られるベースラインを使わなかったが、今回はそれも含めて四角形を描いてみた。
PILの四角形が日本語テキストを微妙に収めきれていない理由はよくわからない。もしかしたら[こちらの方が言及しているバグ](https://tk0miya.hatenablog.com/entry/2014/07/12/005327)が関係しているのかもしれない。

とにかく、独自関数を `cv2.putText()` の *org* と互換性を持たせるには、この部分を修正する必要がある。

## その2

高さの基準を修正したものがこちら。

```Python:
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()` と同様に、左下を指定してテキストを描画することができた。
![cv_jp_font_test_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/ef6a7a32-adf0-f9f8-8e25-35d374a531f1.png)

もっとも、私は `cv2.putText()` で *org* が左下を指定する仕様なのがそもそも気に入らないので `cv2_putText_1()` のほうが好みなのだが。

## その3

`cv2.putText()`でフォントとフォントサイズを指定すれば描画されるテキストのサイズが決まる。それを元に指定したフォントでの適切なサイズを算出すれば、[こちら](https://qiita.com/mo256man/items/c570c645153cae122196)にあるような既存のプログラムを最小限の改造で任意のTrueTypeフォントに変更することができる！ と思ったけど、面倒なのでやめた。。

# 終わりに
関数化しておけば、`cv2.putText()` と同等の使い方で日本語フォントを使うことができる。というお話でした。
ただし、当然、`cv2.imshow()` のウィンドウ名や `cv2.imwrite()` のファイル名で日本語が使えるようになったわけではないので注意。

ところで、`(r, g, b)`というタプルから`(b, g, r)`を作り出すにはどうしたらよいのだろう。
今回の我が関数の中では各要素を取り出して順番を変えた上で新しいタプルを作っているが、エレガントな解法とは言えない。
タプルをリストに変換し、順序を反転してからまたタプルに変換するという方法もあるが、これもエレガントにはほど遠い方法だ。

```Python:
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で日本語フォントを描画する　を関数化する　を汎用的にする](https://qiita.com/mo256man/items/b6e17b5a66d1ea13b5e3)