はじめてのG-API(文字描画編)

# はじめに

dandelion先生( @dandelion1124 ) ならば、[はじめてのG-API(前編)](https://qiita.com/dandelion1124/items/c94d52d7e6cb832321b4)の続き、はじめてのG-API(後編)を執筆してくれる、と信じて！！  (2020/11/08 20:32)

この記事は[OpenCV Advent Calendar 2020](https://qiita.com/advent-calendar/2020/opencv)の3日目の記事です．

# TL;DR

- OpenCV 4.5.0  G-APIのFText Primitiveは、結構制約厳しい。
    - 「文字が出ればよし！」で割り切って使いましょう。
    - リガチャできないよ！
    - フォント種別指定が一種類しかできないよ！
    - パス描画できないよ！（いるかなあ・・・）
    - ２値描画できないよ！

## Motivation

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">あっ，freetype2モジュールネタで書いてくれそうな人だ！あと，OpenCVのテキスト描画周りはちょっと進展あったみたいです．<a href="https://t.co/c89KwQge7M">https://t.co/c89KwQge7M</a></p>&mdash; dandelion (@dandelion1124) <a href="https://twitter.com/dandelion1124/status/1323588271604035584?ref_src=twsrc%5Etfw">November 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://github.com/opencv/opencv/wiki/2020#2020-10-28

> Another round with truetype font suppport. Made use of STB_truetype (tiny, 4Kloc single-file ttf parser and renderer), patched it to support variable fonts, almost refactored previous patch to use STB_truetype instead of freetype. Patch size went down from several megabytes to ~300Kb.

なるほど…

よし、G-APIで文字描画するところだけ書こう！！！

### OpenCV関係の文字描画の歴史
 
これまでのOpenCVの文字描画は歴史を非常に簡単にまとめると、以下の通り。今回は、(3)の話。

(1) imgproc putText （A-Za-z0-9とかぐらいのシンプルなサポートしかない）
　↓　↓　↓　詳細は[cv::putText()周り、ちと見てみる](https://qiita.com/hon_no_mushi/items/c34a07331effced545fb)に昔書きました。
　↓　↓　↓　
　↓　↓　(2) freetype2 wrapper (contrib)　※流行らずメンテナンス不備状態に
　↓　↓　
　↓　(3) [G-API freetype2 APIでサポート] (https://github.com/opencv/opencv/tree/63b154396a9941e2cf24e42045b9f82eeed83824 )
　↓　↓
　↓　(3.1) [freetype2 libraryを3rd party libraryとして抱え込みたい](https://github.com/opencv/opencv/pull/18403)
　↓　↓
　↓　(4) [G-API freetype2 backend](https://github.com/opencv/opencv/tree/e12adcdf08370a404e1baae6464fdaf1eac8460a/modules/gapi/src/backends/render)
　↓
(5) [imgproc STB_truetype](https://github.com/opencv/opencv/commit/d42665d9e20d6f188333f22303c3657e2a39fc84)　これによって、putText()関数で、truetype fontでの文字描画ができるようになる！！！上のはこの話です！

**※ あ、あれ？(5)でSTB_truetypeを搭載すると、(3.1)でfreetype2 library抱え込む検討は要らなくなる……　よし見なかったことにしよう。**

**※ あ、あれ？(5)でSTB_truetypeを搭載すると、(2)でfreetype2 wrapper対応も要らなくなる……　は、削除していい？って確認中。**


# 実際にG-APIで文字描画してみよう

## 実験環境

- ubuntu 20.10
- OpenCV 4.5.0
- GCC10

```bash
kmtr@ubuntu2010:~/work/opencv-4.5.0/build$ gcc --version
gcc (Ubuntu 10.2.0-13ubuntu1) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
kmtr@ubuntu2010:~/work/opencv-4.5.0/build$ pkg-config --version
0.29.2
```

### 情報源

https://docs.opencv.org/master/df/de4/group__gapi__draw.html
https://docs.opencv.org/master/d6/d69/structcv_1_1gapi_1_1wip_1_1draw_1_1FText.html

### コンパイル時の諸注意

CMakeで、"WITH_FREETYPE"を有効にすること。そうしないと実行時に非サポートであることを怒られる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/46f1f293-b7ef-b112-d033-38b0bfbd3ea9.png)


### コード

当該コード中にも記載されていますが、OpenCV 4.5.0のgapiでは、FText primitiveで" "を含む文字列は描画できません。

```c++:test.cpp
#include <opencv2/imgproc.hpp> // cv::FONT*, cv::LINE*, cv::FILLED
#include <opencv2/highgui.hpp> // imwrite
#include <opencv2/gapi.hpp>
#include <opencv2/gapi/render.hpp>
int main(int argc, char *argv[])
{
    if (argc < 2) {
        std::cerr << "Filename required" << std::endl;
        return 1;
    }
    const auto white = cv::Scalar{ 255,255,255};
    const auto red   = cv::Scalar{   0,  0,255};
    const auto black = cv::Scalar{   0,  0,  0};

    cv::Mat img(cv::Size(480, 160), CV_8UC3, white );
    namespace draw = cv::gapi::wip::draw;

    std::vector<draw::Prim> prims;

    // Notice!!
    // In OpenCV 4.5.0, FText doesn't support " "(white space)".
    // Freetype module returns 0x0 bitmap.
    // OpenCV received, but cannot create roi.
    // Exception is happend as
    // "Can't reallocate empty Mat with locked layout (probably due to misused 'const' modifier)

    prims.emplace_back(draw::FText{    // FTEXT primitive
            L"Hello,world",            // Text
            {68,100},                  // Position (a cv::Point)
            50,                        // Font Height
            black                      // Color
        });
    prims.emplace_back(draw::FText{    // FTEXT primitive
            L"Hello,world",            // Text
            {64,96},                   // Position (a cv::Point)
            50,                        // Font Height
            red                        // Color
        });

    draw::render(
        img,
        prims,
        cv::compile_args(
            cv::gapi::wip::draw::freetype_font{"/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf"})
    );

    cv::imwrite(argv[1], img);
    return 0;
}

```

### 出力結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/2efefded-6728-b6a5-a929-aaa5f8cf6061.png)


### おまけ

ft_render.cppにパッチを当てると、4.5.0でも空白を使えるようになります。

```c++:ft_render.cpp
void cv::gapi::wip::draw::FTTextRender::Priv::putText(cv::Mat& mat,
                                                       const std::wstring& text,
                                                       const cv::Point& org,
                                                       int fh)
{
    GAPI_Assert(!FT_Set_Pixel_Sizes(m_face, fh, fh) &&
                "Failed to set pixel size");

    cv::Point pen = org;
    for (const auto& wc : text)
    {
        GAPI_Assert(!FT_Load_Char(m_face, wc, FT_LOAD_RENDER) &&
                    "Failed to load char");
        FT_Bitmap *bitmap = &(m_face->glyph->bitmap);

+       // FIXME: Skip glyph, if size is 0
+       if (bitmap->rows == 0 || bitmap->width == 0) {
+          int advance = (m_face->glyph->advance.x >> 6);
+          pen.x += advance;
+          continue;
+       }

```

# 比較

描画機能それぞれをまとめてみるとこうなる……

あのその、本当に「文字が描画できるだけ」なんですけど…だ、大丈夫なんですかね（心配）

|   | (1) putText |  (2)freetype2 wrapper | (3)G-API FTEXT primitive |
|:-:|:-:|:-:|:-:|
| 入力データ | ASCII | UTF-8 | UTF-16 |
| リガチャ対応 | Not supported | OK | Not supported |
| 描画色指定  | OK | OK | OK |
| 描画線幅指定  | OK  | OK | Not supported |
| 背景色マージ  | OK | OK  | OK |
| 内部フォント指定  | OK | Not supported | Not supported |
| 外部フォント指定  | Not supported | OK | OK |
| 複数外部フォント | Not supported | OK | Not supported |
| 二値ビットマップ描画 | Not supported | OK | Not supported |
| 多値ビットマップ描画 | Not supported | OK | OK |
| パス描画(二値/多値) | OK | OK | Not supported |
| 8UC1 | OK | Not supported | OK(Maybe) |
| 8UC3 | OK | OK | OK(Maybe) |
| 8UC4 | OK | Not supported | OK(Maybe) |

# まとめ（再掲）
- OpenCV 4.5.0  G-APIのFText Primitiveは、結構制約厳しい。
    - 「文字が出ればよし！」で割り切って使いましょう。
    - リガチャできないよ！
    - フォント種別指定が一種類しかできないよ！
    - パス描画できないよ！（いるかなあ・・・）
    - ２値描画できないよ！

oh.... 夢も希望もない・・・？

## 大丈夫だ、問題ない！！

STB_truetypeは（OpenCVで文字描画をしたい）人々にとって、新しい希望になる、はず！！
こちらについては、19日に記事を投稿させていただきます！

## さて、明日の記事は…

@fukushima1981 さんの「OpenCVの並列化フレームワークの謎挙動」です！！謎・・・謎！？いったい何だろう・・・とワクワクしつつ、ご精読ありがとうございました！