OpenCV Advent Calendar 2016 X日目。
OpenCVで文字を描画するcv::putText()先生にはいつもデバッグ等で非常にお世話になっております。本当にありがとうございます!!
なのですが、なかなかその実装をじっくり見る機会が少ないので、これを良いチャンスと思い中身を見てまとめてみました。
が、この記事ではない新しい記事を書いたので参考情報として開示しておきます。
このドキュメントで理解できること
- "Hershey"とは、このストロークフォントを作った作者さん!ありがとうございます!
- OpenCVではグリフ1文字を複数ストロークに分割、座標情報を文字列で表している。
HERSHEYって何?
putText()で指定できるFontFaceには"HERSHEY"がついてる。これは?
- FONT_HERSHEY_SIMPLEX / PLANE / DUPLEX / COMPLEX / TRIPLEX / COMPLEX_SMALL / SCRIPT_SIMPLEX / SCRIPT_COMPLEX
それぞれの見た目の違いを確認したいならば、こちらを。
http://northstar-www.dartmouth.edu/doc/idl/html_6.2/Hershey_Vector_Font_Samples.html
Hershey fonts
まず、言葉の定義を確認するならば、wikipediaが便利ですね! https://en.wikipedia.org/wiki/Hershey_fonts
The Hershey fonts are a collection of vector fonts developed c. 1967 by Dr. Allen V. Hershey at the Naval Weapons Laboratory.[1][2] The fonts are publicly available and have few restrictions.[3] Vector fonts are easily scaled and rotated in two or three dimensions; consequently the Hershey fonts have been widely used in computer graphics and computer-aided design programs.
翻訳する。
(訳)Hershey fontは、Naval Weapons LaboratoryにいたDr. Allen V.Hersheyが1967年に開発したベクターフォントのコレクションである。このフォントは一般的に入手可能であり、いくつかの制限があります。ベクタフォントは、2次元・3次元空間上でスケーリングや回転が容易です。そのため、Hershey fontsはコンピュータグラフィックやCAD(Computer-aided design program)で広く使われてきました。
まとめると
昔から使われている、ストローク/ベクターなフォントで、回転拡大縮小はお手の物!!
それでは、実装のほうも見ていこう!
OpenCV上の実装
extern const char* g_HersheyGlyphs[];
void putText( InputOutputArray _img, const String& text, Point org,
int fontFace, double fontScale, Scalar color,
int thickness, int line_type, bool bottomLeftOrigin )
{
CV_INSTRUMENT_REGION()
if ( text.empty() )
{
return;
}
Mat img = _img.getMat();
const int* ascii = getFontData(fontFace);
double buf[4];
scalarToRawData(color, buf, img.type(), 0);
int base_line = -(ascii[0] & 15);
int hscale = cvRound(fontScale*XY_ONE), vscale = hscale;
if( line_type == CV_AA && img.depth() != CV_8U )
line_type = 8;
if( bottomLeftOrigin )
vscale = -vscale;
int view_x = org.x << XY_SHIFT;
int view_y = (org.y << XY_SHIFT) + base_line*vscale;
std::vector<Point> pts;
pts.reserve(1 << 10);
const char **faces = cv::g_HersheyGlyphs;
for( int i = 0; i < (int)text.size(); i++ )
{
int c = (uchar)text[i];
Point p;
readCheck(c, i, text, fontFace);
const char* ptr = faces[ascii[(c-' ')+1]];
p.x = (uchar)ptr[0] - 'R';
p.y = (uchar)ptr[1] - 'R';
int dx = p.y*hscale;
view_x -= p.x*hscale;
pts.resize(0);
for( ptr += 2;; )
{
if( *ptr == ' ' || !*ptr )
{
if( pts.size() > 1 )
PolyLine( img, &pts[0], (int)pts.size(), false, buf, thickness, line_type, XY_SHIFT );
if( !*ptr++ )
break;
pts.resize(0);
}
else
{
p.x = (uchar)ptr[0] - 'R';
p.y = (uchar)ptr[1] - 'R';
ptr += 2;
pts.push_back(Point(p.x*hscale + view_x, p.y*vscale + view_y));
}
}
view_x += dx;
}
}
グリフデータの構造
- 先頭2文字:文字における左右の位置
- 空白:1ストロークの区切り
- 残り:座標情報。
- 数値は、文字列'R'を0基準にして表現する。
N | O | P | Q | R | S | T | U |
---|---|---|---|---|---|---|---|
-4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 |
実際のグリフデータの一部
const char* g_HersheyGlyphs[] = {
"",
"MWRMNV RMVV PSTS",
"MWOMOV OMSMUNUPSQ OQSQURUUSVOV",
"MXVNTMRMPNOPOSPURVTVVU",
"MWOMOV OMRMTNUPUSTURVOV",
"MWOMOV OMUM OQSQ OVUV",
実際の解釈の例
例:MWOMOV UMUV OQUQ
1 | 2 |
---|---|
M | LeftPos = -5 |
W | RightPos = 5 |
OM | -3,-5 |
OV | -3,4 |
' ' | Stroke End |
UM | 3,-5 |
UV | 3, 4 |
' ' | Stroke End |
OQ | -3,-1 |
UQ | 3,-1 |
もう少し上から見ると
①GlyphCodeとGlyph Dataの変換テーブルを持っておく。
②関数に指定されたFontFaceに応じた、Ascii Code to Glyph Codeの変換テーブルを決定。
③関数に指定されたTextから1文字ずつcに分離し、Glyph Codeを得る。
④このGlyph Codeを順番に描画する事で、全部の文字列が描画される。
マルチバイト文字列対応、というか、日本語対応するには?
既存の実装は、Ascii codeを基準にし、int型で処理している。
この部分の見直しだけで行ける可能性もある一方、
日本語以外のフォントも搭載し始めると、破綻するかも?
まとめ(再掲)
- Hersheyはこのストロークフォントの作者さん!ありがとうございます!
- OpenCVではグリフ1文字を複数ストロークに分割、座標情報を文字列で表している。
所感
非常にシンプルかつ必要なデータサイズ容量も削減できる、ことを実感。
これだけ短いソースコードでグリフ描画を実現していることに感動を覚える。
その一方で、画像への日本語を描画には当該手段以外を適用するべきと感じる。