OpenCV 4.0以上版本无法在图像上绘制中文问题解决
opencv不支持渲染中文字符的本质是不支持对utf-8的字符渲染
opencv库中的cv::putText()函数仅支持对ascii码渲染
想要支持中文或者其他字符的渲染就需要支持Unicode的字符集
三种方式实现opencv对中文字符的渲染
1、使用freetype库
其实早期的opencv是支持Unicode字符渲染的,在c语言版本时代采用的是FreeType库实现的,但由于FreeFype是GPL版权发布的库,和OpenCV版权并不一致,因此目前还没有合并到OpenCV,且因为opencv4.0以上不支持iplimage类型,导致这种方式不能用了
2、编译opencv勾选with Qt
第二种方法是编译opencv的时候,勾选wiht Qt的选项,因为Qt的渲染是支持Unicode字符集的,所以在编译opencv的时候联合编译qt的部分代码可以实现对Unicode的支持。需要重新编译opencv库
3、重写putText函数自行实现
利用windows自带的gdi接口实现绘制中文,已实现,只需添加对应的.h和.cpp文件即可应用,代码如下:
putText.h
#pragma once
#ifndef PUTTEXT_H_
#define PUTTEXT_H_
#include <string>
#include "opencv.hpp"
#include "qt_windows.h"
using namespace std;
using namespace cv;
class putText
{
private:
static void GetStringSize(HDC hDC, const char* str, int* w, int* h);
public:
static void putTextZH(Mat &dst, const char* str, Point org=Point(20,20), Scalar color=Scalar(0,255,0), int fontSize=30,
const char *fn = "Arial", bool italic = false, bool underline = false);
};
#endif // !PUTTEXT_H_
putText.cpp
#include "putText.h"
#include <QDebug>
#include <QString>
#include <stdio.h>
#include <codecvt>
using namespace std;
using namespace cv;
std::string UnicodeToANSI(const std::wstring & wstr)
{
setlocale(LC_CTYPE, "");
std::string ret;
std::mbstate_t state = {};
const wchar_t *src = wstr.data();
size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
if (static_cast<size_t>(-1) != len) {
std::unique_ptr< char [] > buff(new char[len + 1]);
len = std::wcsrtombs(buff.get(), &src, len, &state);
if (static_cast<size_t>(-1) != len) {
ret.assign(buff.get(), len);
}
}
return ret;
}
std::wstring ANSIToUnicode(const std::string & str)
{
setlocale(LC_CTYPE, "");
std::wstring ret;
std::mbstate_t state = {};
const char *src = str.data();
size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
if (static_cast<size_t>(-1) != len) {
std::unique_ptr< wchar_t [] > buff(new wchar_t[len + 1]);
len = std::mbsrtowcs(buff.get(), &src, len, &state);
if (static_cast<size_t>(-1) != len) {
ret.assign(buff.get(), len);
}
}
return ret;
}
std::string UnicodeToUTF8(const std::wstring & wstr)
{
std::string ret;
try {
std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;
ret = wcv.to_bytes(wstr);
} catch (const std::exception & e) {
std::cerr << e.what() << std::endl;
}
return ret;
}
std::wstring UTF8ToUnicode(const std::string & str)
{
std::wstring ret;
try {
std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;
ret = wcv.from_bytes(str);
} catch (const std::exception & e) {
std::cerr << e.what() << std::endl;
}
return ret;
}
void putText::GetStringSize(HDC hDC, const char* str, int* w, int* h)
{
SIZE size;
GetTextExtentPoint32A(hDC, str, strlen(str), &size);
if (w != 0) *w = size.cx;
if (h != 0) *h = size.cy;
}
/*
* 20210630 wenbo
* Mat dst:原图,
* const char* str:绘制文字内容,
* Point org:起点位置,
* Scalar color:字体颜色,
* int fontSize:字体大小(字号),
* const char* fn:字体类型,
* bool italic:斜体,
* bool underline:下划线
*/
void putText::putTextZH(Mat &dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline)
{
//对输入字符进行utf转ansi, 直接利用系统默认字符集编码,只要能打出来就能写上去
wstring wstr;
wstr=UTF8ToUnicode(str);
string strin=UnicodeToANSI(wstr);
str=strin.c_str();
//判断图像通道数
if(dst.channels()==1) cvtColor(dst,dst,COLOR_GRAY2BGR);
CV_Assert(dst.data != 0 && (dst.channels() == 1 || dst.channels() == 3));
int x, y, r, b;
if (org.x > dst.cols || org.y > dst.rows) return;
x = org.x < 0 ? -org.x : 0;
y = org.y < 0 ? -org.y : 0;
LOGFONTA lf;
lf.lfHeight = fontSize;
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = 5;
lf.lfItalic = italic; //斜体
lf.lfUnderline = underline; //下划线
lf.lfStrikeOut = 0;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = PROOF_QUALITY;
lf.lfPitchAndFamily = 0;
strcpy_s(lf.lfFaceName, fn);
HFONT hf = CreateFontIndirectA(&lf);
HDC hDC = CreateCompatibleDC(0);
HFONT hOldFont = (HFONT)SelectObject(hDC, hf);
int strBaseW = 0, strBaseH = 0;
int singleRow = 0;
char buf[1 << 12];
strcpy_s(buf, str);
char *bufT[1 << 12]; // 这个用于分隔字符串后剩余的字符,可能会超出。
//处理多行
{
int nnh = 0;
int cw, ch;
const char* ln = strtok_s(buf, "\n", bufT);
while (ln != 0)
{
GetStringSize(hDC, ln, &cw, &ch);
strBaseW = max(strBaseW, cw);
strBaseH = max(strBaseH, ch);
ln = strtok_s(0, "\n", bufT);
nnh++;
}
singleRow = strBaseH;
strBaseH *= nnh;
}
if (org.x + strBaseW < 0 || org.y + strBaseH < 0)
{
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hDC);
return;
}
r = org.x + strBaseW > dst.cols ? dst.cols - org.x - 1 : strBaseW - 1;
b = org.y + strBaseH > dst.rows ? dst.rows - org.y - 1 : strBaseH - 1;
org.x = org.x < 0 ? 0 : org.x;
org.y = org.y < 0 ? 0 : org.y;
BITMAPINFO bmp = { 0 };
BITMAPINFOHEADER& bih = bmp.bmiHeader;
int strDrawLineStep = strBaseW * 3 % 4 == 0 ? strBaseW * 3 : (strBaseW * 3 + 4 - ((strBaseW * 3) % 4));
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = strBaseW;
bih.biHeight = strBaseH;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = BI_RGB;
bih.biSizeImage = strBaseH * strDrawLineStep;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
void* pDibData = 0;
HBITMAP hBmp = CreateDIBSection(hDC, &bmp, DIB_RGB_COLORS, &pDibData, 0, 0);
CV_Assert(pDibData != 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
SetTextColor(hDC, RGB(255, 255, 255));
SetBkColor(hDC, 0);
strcpy_s(buf, str);
const char* ln = strtok_s(buf, "\n", bufT);
int outTextY = 0;
while (ln != 0)
{
TextOutA(hDC, 0, outTextY, ln, strlen(ln));
outTextY += singleRow;
ln = strtok_s(0, "\n", bufT);
}
uchar* dstData = (uchar*)dst.data;
int dstStep = dst.step / sizeof(dstData[0]);
unsigned char* pImg = (unsigned char*)dst.data + org.x * dst.channels() + org.y * dstStep;
unsigned char* pStr = (unsigned char*)pDibData + x * 3;
for (int tty = y; tty <= b; ++tty)
{
unsigned char* subImg = pImg + (tty - y) * dstStep;
unsigned char* subStr = pStr + (strBaseH - tty - 1) * strDrawLineStep;
for (int ttx = x; ttx <= r; ++ttx)
{
for (int n = 0; n < dst.channels(); ++n) {
double vtxt = subStr[n] / 255.0;
int cvv = vtxt * color.val[n] + (1 - vtxt) * subImg[n];
subImg[n] = cvv > 255 ? 255 : (cvv < 0 ? 0 : cvv);
}
subStr += 3;
subImg += dst.channels();
}
}
SelectObject(hDC, hOldBmp);
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hBmp);
DeleteDC(hDC);
//OutputDebugStringA("putTextZH finish");
}