search
LoginSignup
7

More than 1 year has passed since last update.

posted at

updated at

putText()関数でtruetype fontを使ったテキスト描画ができるようになりました!

この記事はOpenCV Advent Calendar 2020の19日目の記事です.

続編→ putText()関数でtruetype fontを使った "日本語" 描画ができるようになります!

TL;DR

  • OpenCV imgprocのputText()関数で、STB Libraryによるtruetype fontを使ったテキスト描画ができるようになりました!
    • UTF-8入力はうれしい。
    • 追加ライブラリもいらない!
    • フォントデータもあらかじめ組み込まれている、
    • harfbuzzライブラリがあれば、リガチャできる!(これは大きい)
    • 内蔵フォントでほぼ何も考えなくてもきれいに描画できる!
    • 外部フォント指定は…できないかも? 複数個指定できます!!(2020/12/20 6:15更新)
    • G-API対応もできている(っぽい)
    • これから更なる発展が期待できる!!

2020/12/19 7:19 追記。
「なりました」と書いているけど、厳密には「なります(未来系)」が正しい気が若干しますので、一応メモ。

2020/12/20 6:18 追記。
「外部フォント指定もできますよ!」とのことでしたので、こちらも追記しました。最強ですね……

ありがとうございます!

Motivation (使いまわし)

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.

なるほど!!STB! なんだこれ!から調査をしてみる。

STB_truetypeとは?

  • https://github.com/nothings/stb
  • single-file public domain (or MIT licensed) libraries for C/C++
  • 1つのファイルだけで、色々な機能を実現できる(しかもMIT License... ありがたや、ありがたや...)
    • stb_image.h (7762 byte) : image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC
    • stb_truetype.h (5011 byte) : parse, decode, and rasterize characters from truetype fonts
    • stb_sprintf.h ( 1879 byte) : fast sprintf, snprintf for C/C++
    • etc...

実際に環境構築してみよう!

注意事項(2020/12/19 7:20追記)

OpenCV 4.5.0 と、OpenCV Next(実質5.0)は共存うまくできない・・・かも?このあたりは要調査ですが、nextをテストするときは4.5.0を sudo make uninstall しておいたほうが無難ですね。

OpenCV nextのダウンロード

OpenCVのbranch構成

まず、OpenCVのbranch構成はこうなっている。

  • master ( default )
  • 2.4
  • 3.4
  • next ★

2020/12/2 現在、この修正はnext branchにしか入っていないので、こちらをcloneする。

next branchのcloneとconfiguration

このあたりはいつもの手順。

$ git clone -b next https://github.com/opencv/opencv.git
$ mv opencv opencv_next
$ cd opencv_next
$ mkdir build ; cd build 
$ cmake .. ; cmake-gui ..

ソースコード

……これ、1行もHERSHEYのコードから直してないんですけど…

#include <opencv2/imgproc.hpp> // cv::FONT*, cv::LINE*, cv::FILLED
#include <opencv2/highgui.hpp> // imwrite
#include <iostream>

using namespace cv;

int main(int argc, char *argv[])
{
    if (argc < 2) {
        std::cerr << "Filename required" << std::endl;
        return 1;
    }

    String text = "Funny text inside the box";
    int fontFace = FONT_HERSHEY_SCRIPT_SIMPLEX;
    double fontScale = 2;
    int thickness = 3;

    Mat img(600, 800, CV_8UC3, Scalar::all(0));

    int baseline=0;
    Size textSize = getTextSize(text, fontFace,
                                fontScale, thickness, &baseline);
    baseline += thickness;

    // center the text
    Point textOrg((img.cols - textSize.width)/2,
                  (img.rows + textSize.height)/2);

    // draw the box
    rectangle(img, textOrg + Point(0, baseline),
              textOrg + Point(textSize.width, -textSize.height),
              Scalar(0,0,255));
    // ... and the baseline first
    line(img, textOrg + Point(0, thickness),
         textOrg + Point(textSize.width, thickness),
         Scalar(0, 0, 255));

    // then put the text itself
    putText(img, text, textOrg, fontFace, fontScale,
            Scalar::all(255), thickness, 8);

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

実行結果

a.png

お、おおお!!! 骨骨じゃない!!truetype fontだああああ!!!

でもでも、色々疑問が残りますよね!!

  • フォントファイル指定してなかったよなあ・・・
  • フォントファイルはどこからでてきたんだ?

<参考>
OpenCV 4.5.0 で、line_width = 1 にするとこういう画像になる。

a.png

もうちょっと出力例

これをベースに、OpenCV 4.5.0 と nextの出力例を比較してみたかったけど、

OpenCV 4.5.0, linewidth = 1

image.png

OpenCV 4.5.0, linewidth = 2

image.png

next, line width = 1

image.png

next, line width = 2

image.png

ソースコード

http://opencv.jp/cookbook/opencv_drawing.html#id8 を一部修正。

a.cpp
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::Mat::zeros(500, 500, CV_8UC3);

  int face[] = {cv::FONT_HERSHEY_SIMPLEX, cv::FONT_HERSHEY_PLAIN, cv::FONT_HERSHEY_DUPLEX, cv::FONT_HERSHEY_COMPLEX,
                cv::FONT_HERSHEY_TRIPLEX, cv::FONT_HERSHEY_COMPLEX_SMALL, cv::FONT_HERSHEY_SCRIPT_SIMPLEX,
                cv::FONT_HERSHEY_SCRIPT_COMPLEX, cv::FONT_ITALIC};

#define CV_AA 16

  // 画像,テキスト,位置(左下),フォント,スケール,色,線太さ,種類
  cv::putText(img, "OpenCV", cv::Point(50,50), face[0], 1.2, cv::Scalar(0,0,200), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,100), face[1], 1.2, cv::Scalar(0,200,0), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,150), face[2], 1.2, cv::Scalar(200,0,0), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,200), face[3], 1.2, cv::Scalar(0,100,100), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,250), face[4], 1.2, cv::Scalar(100,100,0), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,300), face[5], 1.2, cv::Scalar(100,0,100), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,350), face[6], 1.2, cv::Scalar(100,100,100), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(50,400), face[7], 1.2, cv::Scalar(100,100,200), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,50), face[0]|face[8], 1.2, cv::Scalar(100,200,100), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,100), face[1]|face[8], 1.2, cv::Scalar(200,100,100), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,150), face[2]|face[8], 1.2, cv::Scalar(200,200,100), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,200), face[3]|face[8], 1.2, cv::Scalar(200,100,200), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,250), face[4]|face[8], 1.2, cv::Scalar(100,200,200), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,300), face[5]|face[8], 1.2, cv::Scalar(100,200,255), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,350), face[6]|face[8], 1.2, cv::Scalar(100,255,200), 1, CV_AA);
  cv::putText(img, "OpenCV", cv::Point(300,400), face[7]|face[8], 1.2, cv::Scalar(255,200,100), 1, CV_AA);

  cv::imwrite ("a.png", img);
}

外部フォントも描画できる (2020/12/20 6:22追記)

記事内の1つ目のソースコードをちょっと変更して外部フォントが使えることを確認しました。
getTextSizeの方もオーバーロードが増えていたのでソースも読まずに見た目だけで調整してみました。が、これで合っているか自信がないです・・・

とコメント頂きましたので、

    int fontFace = FONT_HERSHEY_SCRIPT_SIMPLEX;
  ↓
    FontFace fontFace("フォントファイル");

に置き換える事で、日本語の描画もできる。素敵!!ご指摘、ありがとうございました!

ちょっとライブラリをもう少し深堀してみる。

opencv/modules/imgproc/fonts

gz圧縮されたフォントデータが格納されている。

opencv/modules/imgproc/src/drawing.cpp

従来のdrawing.cppでは、引数省略したときに全部引数を指定した場合の関数を呼び出すコードが含まれます。

おや?CvFont

CV_IMPL void
cvPutText( CvArr* _img, const char *text, CvPoint org, const CvFont *_font, CvScalar color )
{
    cv::Mat img = cv::cvarrToMat(_img);
    CV_Assert( text != 0 && _font != 0);
    cv::putText( img, text, org, _font->font_face, (_font->hscale+_font->vscale)*0.5,
                color, _font->thickness, _font->line_type,
                CV_IS_IMAGE(_img) && ((IplImage*)_img)->origin != 0 );
}

opencv/modules/imgproc/src/drawing_text.cpp

旧putText()

新規drawing_text.cpp で、いつも通りのputText()を呼び出すと、hersheyToTruetype()して、新しいputText()を呼び出すようになっている。これですね!

void putText( InputOutputArray _img, const String& text, Point org,
              int fontFace, double fontScale, Scalar color,
              int thickness, int, bool bottomLeftOrigin )

{
    String ttname;
    int ttsize = 0;
    int ttweight = 0;
    hersheyToTruetype(fontFace, fontScale, thickness, ttname, ttsize, ttweight);
    FontFace fface(ttname);
    PutTextFlags flags = bottomLeftOrigin ? PUT_TEXT_ORIGIN_BL : PUT_TEXT_ORIGIN_TL;
    putText(_img, text, org, color, fface, ttsize, ttweight, flags);
}

ここで、hersheyToTruetype()関数の中身を見ると、従来のfont指定を書き換えてくれる。だから、どのフォントを使えばいいのか指定をしなくても済んだのですね!

static void hersheyToTruetype(int fontFace, double fontScale, int thickness,
                              String& ttname, int& ttsize, int& ttweight)
{
    double sf = 0;
    switch(fontFace & ~FONT_ITALIC)
    {
    case FONT_HERSHEY_PLAIN:
        ttname = "sans";
        sf = 6.6;
        ttweight = thickness <= 1 ? 400 : 800;
        break;
    case FONT_HERSHEY_SIMPLEX:
        ttname = "sans";
        sf = 3.7;
        ttweight = thickness <= 1 ? 400 : 600;
        break;
    case FONT_HERSHEY_DUPLEX:
        ttname = "sans";
        sf = 3.7;
        ttweight = thickness <= 1 ? 600 : 800;
        break;
:
:

新putText()

新しいputText()では、fontRenderEngine instanceのputTextを呼び出すようになっています。

Point putText(InputOutputArray img_, const String& str,
             Point org, Scalar color_,
             FontFace& fontface, int size,
             int weight, PutTextFlags flags, Range wrap)
{
    uchar color[] =
    {
        saturate_cast<uchar>(color_[0]), saturate_cast<uchar>(color_[1]),
        saturate_cast<uchar>(color_[2]), saturate_cast<uchar>(color_[3])
    };
    Mat img = img_.getMat();
    int nch = img.channels();
    CV_Assert(img.depth() == CV_8U);
    CV_Assert(nch == 1 || nch == 3 || nch == 4);

    return fontRenderEngine.putText_(img, img.size(), str, org, color, fontface, size,
                                     weight, flags, wrap, true, 0);
}

STB_truetypeでできること、できないこと

(1) putText (2)freetype2 wrapper (3)G-API FTEXT primitive (4) putText(STB_truetype)
追加ライブラリ Not needed Needed Not needed Not needed
入力データ ASCII UTF-8 UTF-16 UTF-8
リガチャ対応 Not supported OK Not supported OK
描画色指定 OK OK OK OK
描画線幅指定 OK OK Not supported 若干Supported
内部フォント指定 OK Not supported Not supported OK
外部フォント指定 Not supported OK OK NG OK!!
複数外部フォント Not supported OK Not supported NG OK!!
二値ビットマップ描画 Not supported OK Not supported Not supported
多値ビットマップ描画 Not supported OK OK OK
パス描画(二値/多値) OK OK Not supported Not supported
G-API対応 OK NG OK OK!!!!
Glyph Cache (不要) Not Supported Supported Supported
8UC1 OK Not supported OK(Maybe) OK
8UC3 OK OK OK(Maybe) OK
8UC4 OK Not supported OK(Maybe) OK

FontFaceがちょっと気になる…

これを使えば外部フォントも利用できるかも?と考えたのですが、どうにも"sans"とかの指定だけで、ファイル名そのものが指定できないようにも見える。このあたりは正規版がでてから検証が必要ですね。

ここは、もうできる!!で終了です (2020/12/20 6:22)

まとめ

  • OpenCV imgprocのputText()関数で、STB Libraryによるtruetype fontを使ったテキスト描画ができるようになりました!
    • UTF-8入力はうれしい。
    • 追加ライブラリもいらない!
    • フォントデータもあらかじめ組み込まれている、
    • harfbuzzライブラリがあれば、リガチャできる!(これは大きい)
    • 内蔵フォントでほぼ何も考えなくてもきれいに描画できる!
    • 外部フォント指定は…できないかも? 複数個指定できます!!(2020/12/20 6:15更新)
    • G-API対応もできている(っぽい)
    • これから更なる発展が期待できる!!

ご精読ありがとうございました!

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
What you can do with signing up
7