Adapting CvxText for Drawing Chinese Characters in OpenCV 4.5
Drawing Chinese characters on images in real-time is a common requirement. When you cannot avoid this task, drawing directly onto the image buffer is usually the most straightforward approach, as it avoids the overhead of complex GUI frameworks. The classic solution is to use freetype combined with CvxText. However, after upgrading to OpenCV 4.5, you will likely find that legacy CvxText implementations fail to compile. This article covers the necessary adjustments to make it compatible.
Prerequisites
- C/C++ build environment
- OpenCV library
- FreeType library: Pre-compiled (I used version 2.9.1, available at https://freetype.org/)
- Font file: e.g.,
simhei.ttf
Updating CvxText Code
The primary issues when migrating legacy CvxText code to OpenCV 4.5 involve header structure changes and stricter type handling.
1. Header Inclusion
OpenCV 4.x reorganized its directory structure, removing the opencv subdirectory and consolidating headers under opencv2. Update your includes as follows:
#include "opencv2/core/core.hpp"
#include "opencv2/core/core_c.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
Including core_c.h is essential, as it provides support for the C-style types (like CvScalar) that CvxText relies on.
2. Handling CvScalar
In OpenCV 4.x, CvScalar cannot be implicitly converted to cv::Scalar. You must perform explicit conversions in your putText methods:
int CvxText::putText(cv::Mat &frame, const char *text, CvPoint pos)
{
CvScalar s = {255, 255, 255};
return putText(frame, text, pos, s);
}
int CvxText::putText(cv::Mat &frame, const wchar_t *text, CvPoint pos)
{
CvScalar s = {255, 255, 255};
return putText(frame, text, pos, s);
}
3. Converting cv::Mat to IplImage
Direct casting &(IplImage)frame no longer works in OpenCV 4.x. Use the cvIplImage function provided in core_c.h instead:
IplImage* img = NULL;
img = &(cvIplImage(frame));
Usage
Initialization:
CvxText text("path/to/your/font.ttf");
Interface:
int putText(cv::Mat &frame, const char *text, CvPoint pos);
Full Source Code
The following code is adapted for OpenCV 4.5 and should work on any 4.x release.
CvxText.h
#ifndef CVX_TEXT_H
#define CVX_TEXT_H
#include <ft2build.h>
#include FT_FREETYPE_H
#include "opencv2/core/core.hpp"
#include "opencv2/core/core_c.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
class CvxText
{
CvxText& operator=(const CvxText&);
public:
CvxText(const char *freeType);
virtual ~CvxText();
void getFont(int *type, CvScalar *size = NULL, bool *underline = NULL, float *diaphaneity = NULL);
void setFont(int *type, CvScalar *size = NULL, bool *underline = NULL, float *diaphaneity = NULL);
void restoreFont();
int putText(cv::Mat &frame, const char *text, CvPoint pos);
int putText(cv::Mat &frame, const wchar_t *text, CvPoint pos);
int putText(cv::Mat &frame, const char *text, CvPoint pos, CvScalar color);
int putText(cv::Mat &frame, const wchar_t *text, CvPoint pos, CvScalar color);
private:
void putWChar(cv::Mat &frame, wchar_t wc, CvPoint &pos, CvScalar color);
private:
FT_Library m_library;
FT_Face m_face;
int m_fontType;
CvScalar m_fontSize;
bool m_fontUnderline;
float m_fontDiaphaneity;
};
#endif
CvxText.cpp
#include "CvxText.h"
#include <wchar.h>
#include <assert.h>
#include <locale.h>
#include <ctype.h>
using namespace cv;
CvxText::CvxText(const char *freeType)
{
assert(freeType != NULL);
if (FT_Init_FreeType(&m_library)) throw;
if (FT_New_Face(m_library, freeType, 0, &m_face)) throw;
restoreFont();
setlocale(LC_ALL, "");
}
CvxText::~CvxText()
{
FT_Done_Face(m_face);
FT_Done_FreeType(m_library);
}
void CvxText::getFont(int *type, CvScalar *size, bool *underline, float *diaphaneity)
{
if (type) *type = m_fontType;
if (size) *size = m_fontSize;
if (underline) *underline = m_fontUnderline;
if (diaphaneity) *diaphaneity = m_fontDiaphaneity;
}
void CvxText::setFont(int *type, CvScalar *size, bool *underline, float *diaphaneity)
{
if (type && *type >= 0) m_fontType = *type;
if (size) {
m_fontSize.val[0] = fabs(size->val[0]);
m_fontSize.val[1] = fabs(size->val[1]);
m_fontSize.val[2] = fabs(size->val[2]);
m_fontSize.val[3] = fabs(size->val[3]);
}
if (underline) m_fontUnderline = *underline;
if (diaphaneity) m_fontDiaphaneity = *diaphaneity;
}
void CvxText::restoreFont()
{
m_fontType = 0;
m_fontSize.val[0] = 40;
m_fontSize.val[1] = 0.5;
m_fontSize.val[2] = 0.1;
m_fontSize.val[3] = 0;
m_fontUnderline = false;
m_fontDiaphaneity = 1.0;
FT_Set_Pixel_Sizes(m_face, (int)m_fontSize.val[0], 0);
}
int CvxText::putText(cv::Mat &frame, const char *text, CvPoint pos)
{
CvScalar s = {255, 255, 255};
return putText(frame, text, pos, s);
}
int CvxText::putText(cv::Mat &frame, const wchar_t *text, CvPoint pos)
{
CvScalar s = {255, 255, 255};
return putText(frame, text, pos, s);
}
int CvxText::putText(cv::Mat &frame, const char *text, CvPoint pos, CvScalar color)
{
if (frame.empty() || text == NULL) return -1;
int i;
for (i = 0; text[i] != '\0'; ++i) {
wchar_t wc = text[i];
if (!isascii(wc)) mbtowc(&wc, &text[i++], 2);
putWChar(frame, wc, pos, color);
}
return i;
}
int CvxText::putText(cv::Mat &frame, const wchar_t *text, CvPoint pos, CvScalar color)
{
if (frame.empty() || text == NULL) return -1;
int i;
for (i = 0; text[i] != '\0'; ++i) {
putWChar(frame, text[i], pos, color);
}
return i;
}
void CvxText::putWChar(cv::Mat &frame, wchar_t wc, CvPoint &pos, CvScalar color)
{
IplImage* img = &(cvIplImage(frame));
FT_UInt glyph_index = FT_Get_Char_Index(m_face, wc);
FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT);
FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_MONO);
FT_GlyphSlot slot = m_face->glyph;
int rows = slot->bitmap.rows;
int cols = slot->bitmap.width;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
int off = ((img->origin == 0) ? i : (rows - 1 - i)) * slot->bitmap.pitch + j / 8;
if (slot->bitmap.buffer[off] & (0xC0 >> (j % 8))) {
int r = (img->origin == 0) ? pos.y - (rows - 1 - i) : pos.y + i;
int c = pos.x + j;
if (r >= 0 && r < img->height && c >= 0 && c < img->width) {
CvScalar scalar = cvGet2D(img, r, c);
float p = m_fontDiaphaneity;
for (int k = 0; k < 4; ++k)
scalar.val[k] = scalar.val[k] * (1 - p) + color.val[k] * p;
cvSet2D(img, r, c, scalar);
}
}
}
}
double space = m_fontSize.val[0] * m_fontSize.val[1];
double sep = m_fontSize.val[0] * m_fontSize.val[2];
pos.x += (int)((cols ? cols : space) + sep);
}