この記事は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 (使いまわし)
あっ,freetype2モジュールネタで書いてくれそうな人だ!あと,OpenCVのテキスト描画周りはちょっと進展あったみたいです.https://t.co/c89KwQge7M
— dandelion (@dandelion1124) November 3, 2020
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;
}
実行結果
お、おおお!!! 骨骨じゃない!!truetype fontだああああ!!!
でもでも、色々疑問が残りますよね!!
- フォントファイル指定してなかったよなあ・・・
- フォントファイルはどこからでてきたんだ?
<参考>
OpenCV 4.5.0 で、line_width = 1 にするとこういう画像になる。
もうちょっと出力例
これをベースに、OpenCV 4.5.0 と nextの出力例を比較してみたかったけど、
OpenCV 4.5.0, linewidth = 1
OpenCV 4.5.0, linewidth = 2
next, line width = 1
next, line width = 2
ソースコード
http://opencv.jp/cookbook/opencv_drawing.html#id8 を一部修正。
#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圧縮されたフォントデータが格納されている。
- https://github.com/opencv/opencv/tree/next/modules/imgproc/fonts
- Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik),
- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
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 |
|
複数外部フォント | Not supported | OK | Not supported |
|
二値ビットマップ描画 | 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対応もできている(っぽい)
- これから更なる発展が期待できる!!
ご精読ありがとうございました!