Jump to content
Compvision.ru
Smorodov

UNICODE и OpenCV (Вывод русских надписей)

Recommended Posts

Предлагаю поразмыслить на досуге над выводом русских букв и произвольных шрифтов. Привожу ниже код, использующий библиотеку freetype, но результат, надо сказать не очень радует. В первую очередь огорчает прорисовка контуров средствами opencv. Если кто решил вопрос человеческой отрисовки шрифтов, прошу поделиться опытом :)

 

Вот еще полезная ссылка в тему: http://www.codeproject.com/KB/GDI/hersheyfont.aspx

 

post-1-0-57373400-1314212732_thumb.png

 

// OpenCVFont.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "opencv2/opencv.hpp"
#include <ft2build.h>
#include FT_FREETYPE_H
FT_Library  library;
FT_Face     face;      /* handle to face object */
using namespace cv; // all the new API is put into "cv" namespace. Export its content
using namespace std;
typedef vector<Point> cont;
vector<cont> contours;
vector<Vec4i> hierarchy;
//----------------------------------------------------------
void cubic_bezier(cont& points, const Point2f & from, const Point2f & cp1, const Point2f & cp2, const Point2f & to, int curveResolution )
{
	points.push_back(Point2f(from.x,from.y));
	if (points.size() > 0)
	{
		float x0 = points[points.size()-1].x;
		float y0 = points[points.size()-1].y;
		float   ax, bx, cx;
		float   ay, by, cy;
		float   t, t2, t3;
		float   x, y;
		// polynomial coefficients
		cx = 3.0f * (cp1.x - x0);
		bx = 3.0f * (cp2.x - cp1.x) - cx;
		ax = to.x - x0 - cx - bx;
		cy = 3.0f * (cp1.y - y0);
		by = 3.0f * (cp2.y - cp1.y) - cy;
		ay = to.y - y0 - cy - by;
		for (int i = 0; i < curveResolution; i++)
		{
			t 	=  (float)i / (float)(curveResolution-1);
			t2 = t * t;
			t3 = t2 * t;
			x = (ax * t3) + (bx * t2) + (cx * t) + x0;
			y = (ay * t3) + (by * t2) + (cy * t) + y0;
			points.push_back(Point(x,y));
		}
	}
}
//----------------------------------------------------------
void quad_bezier(cont& points, float x1, float y1, float x2, float y2, float x3, float y3, int curveResolution)
{
	points.push_back(Point2f(x1,y1));
	for(int i=0; i <= curveResolution; i++)
	{
		double t = (double)i / (double)(curveResolution);
		double a = (1.0 - t)*(1.0 - t);
		double b = 2.0 * t * (1.0 - t);
		double c = t*t;
		double x = a * x1 + b * x2 + c * x3;
		double y = a * y1 + b * y2 + c * y3;
		points.push_back(Point(x, y));
	}
}
//---------------------------------------------------------------------
static void makeContoursForCharacter(vector<cont> &result,FT_Face &face)
{
	result.clear();
	cont Cont;
	int nContours	= face->glyph->outline.n_contours;
	int startPos	= 0;
	char * tags		= face->glyph->outline.tags;
	FT_Vector * vec = face->glyph->outline.points;
	for(int k = 0; k < nContours; k++)
	{
		Cont.clear();
		if( k > 0 )
		{
			startPos = face->glyph->outline.contours[k-1]+1;
		}
		int endPos = face->glyph->outline.contours[k]+1;
		Point2f lastPoint;
		for(int j = startPos; j < endPos; j++)
		{
			if( FT_CURVE_TAG(tags[j]) == FT_CURVE_TAG_ON )
			{
				lastPoint.x=(float)vec[j].x;
				lastPoint.y=(float)-vec[j].y;
				Cont.push_back(Point(lastPoint.x, lastPoint.y));
			}
			else
			{
				if( FT_CURVE_TAG(tags[j]) == FT_CURVE_TAG_CUBIC )
				{
					int prevPoint = j-1;
					if( j == 0)
					{
						prevPoint = endPos-1;
					}
					int nextIndex = j+1;
					if( nextIndex >= endPos)
					{
						nextIndex = startPos;
					}
					Point2f nextPoint( (float)vec[nextIndex].x,  -(float)vec[nextIndex].y );
					//we need two control points to draw a cubic bezier
					bool lastPointCubic =  ( FT_CURVE_TAG(tags[prevPoint]) != FT_CURVE_TAG_ON ) && ( FT_CURVE_TAG(tags[prevPoint]) == FT_CURVE_TAG_CUBIC);
					if( lastPointCubic )
					{
						Point2f controlPoint1((float)vec[prevPoint].x,	(float)-vec[prevPoint].y);
						Point2f controlPoint2((float)vec[j].x, (float)-vec[j].y);
						Point2f nextPoint((float) vec[nextIndex].x,	-(float) vec[nextIndex].y);
						cubic_bezier(Cont, lastPoint, controlPoint1, controlPoint2, nextPoint, 8);
					}
				}
				else
				{
					Point2f conicPoint( (float)vec[j].x,  -(float)vec[j].y );
					//If the first point is connic and the last point is connic then we need to create a virutal point which acts as a wrap around
					if( j == startPos )
					{
						bool prevIsConnic = (  FT_CURVE_TAG( tags[endPos-1] ) != FT_CURVE_TAG_ON ) && ( FT_CURVE_TAG( tags[endPos-1]) != FT_CURVE_TAG_CUBIC );
						if( prevIsConnic )
						{
							Point2f lastConnic((float)vec[endPos - 1].x, (float)-vec[endPos - 1].y);
							lastPoint.x = (conicPoint.x + lastConnic.y) / 2;
							lastPoint.y = (conicPoint.x + lastConnic.y) / 2;
						}
   				}
					//bool doubleConic = false;
					int nextIndex = j+1;
					if( nextIndex >= endPos)
					{
						nextIndex = startPos;
					}
					Point2f nextPoint( (float)vec[nextIndex].x,  -(float)vec[nextIndex].y );
					bool nextIsConnic = (  FT_CURVE_TAG( tags[nextIndex] ) != FT_CURVE_TAG_ON ) && ( FT_CURVE_TAG( tags[nextIndex]) != FT_CURVE_TAG_CUBIC );
					//create a 'virtual on point' if we have two connic points
					if( nextIsConnic )
					{
						nextPoint.x = (conicPoint.x + nextPoint.x) / 2;
						nextPoint.y = (conicPoint.y + nextPoint.y) / 2;
					}
					quad_bezier(Cont, lastPoint.x, lastPoint.y, conicPoint.x, conicPoint.y, nextPoint.x, nextPoint.y, 8);
					if( nextIsConnic )
					{
						lastPoint = nextPoint;
					}
				}
			}
			//end for
		}
		result.push_back(Cont);
	}
}
//-----------------------------------------------------------------------
void PrintString(Mat& img,std::wstring str,int x,int y)
{
	FT_Bool       use_kerning=0;
	FT_UInt       previous=0;
	use_kerning = FT_HAS_KERNING( face );
	float posx=0;
	for(int k=0;k<str.length();k++)
	{
		int glyph_index = FT_Get_Char_Index( face, str.c_str()[k] );
		FT_GlyphSlot  slot = face->glyph;  /* a small shortcut */
		float dx=slot->advance.x/64;
		FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
		makeContoursForCharacter(contours,face);
		if ( use_kerning && previous && glyph_index )
		{
			FT_Vector  delta;
			FT_Get_Kerning( face, previous, glyph_index, FT_KERNING_DEFAULT, &delta );
			posx += (delta.x/64);
		}
		for(int i=0;i<contours.size();i++)
		{
			for(int j=0;j<contours[i].size();j++)
			{
				contours[i][j].x=(float)contours[i][j].x/64.0;
				contours[i][j].y=(float)contours[i][j].y/64.0;
			}
		}
		posx+=(dx);
		drawContours(img,contours,-1, Scalar(255,255,255), -1,CV_AA,noArray(),INT_MAX,cv::Point(posx+x,y));
		contours.clear();
		previous = glyph_index;
	}
}
//-----------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
	FT_Init_FreeType( &library );
	FT_New_Face( library,"arial.ttf",0,&face );
	FT_Set_Pixel_Sizes(face,40,0);
	FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	Mat img(480,640,CV_8UC3);
	PrintString(img,L"Привет!",100,100);
	//	http://freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline
	//printf("%d",x);
	cv::imshow("win",img);
	cv::waitKey(0);
	//CvPoint2D32f
	return 0;
}

 

 

Share this post


Link to post
Share on other sites

Одной из самых мощных библиотек считается AGG. Но я не пробовал.

Было дело, рисовал весь алфавит средствами GDI на картинке, а после сам рисовал текст: вырезал каждую букву, учитывая отступы. Но подход тяжёлый: на разных версиях Windows шрифты с одними и теми же флагами рисуются по-разному.

  • Like 1

Share this post


Link to post
Share on other sites

http://freetype.sourceforge.net/freetype1/image/freetest.png

ну чо то не впечатляет, сглаживания то нет наверно.

http://freetype.sourceforge.net/freetype1/image/scrshot3.png

а может и есть)

а так то наверно, можно брать картинку из opencv и на ней рисовать сторонними средствами.

а QT тоже не умеет русский?

http://opencv.willowgarage.com/documentation/cpp/highgui_qt_new_functions

  • Like 1

Share this post


Link to post
Share on other sites

Спасибо. AGG - действительно мощная штука. Хотелось бы рисовать векторным способом. При рендеринге шрифта средствами библиотеки freetype (она умеет сама делать из букв битмапы), нужны будут дополнительные танцы с бубном для наложения букв с эффектом прозрачности, при этом, скорее всего возникнут проблемы с переходом на границах.

Вероятно, здесь действительно наиболее подходящим способом является адаптация шрифтов Hershey (плоттерный тип шрифта) который, кстати говоря, содержится в файле, лежащем в директории modules/core/tables.cpp (принцип описания шрифтов стандартный можно найти например здесь: http://paulbourke.net/dataformats/hershey/ ). Там же есть и русские буквы, только пока не сообразил как прикрутить. Сама функция отрисовки текста содержится в файле drawing.cpp в той же директории.

ЗЫ: QT надо посмотреть, может правда получится оттуда содрать что нибудь.

ЗЫЗЫ: Посмотрел, как реализовано в QT. Там рисуется средствами класса QPainter. Можно применить похожий подход для VS - захватить в BITMAP вывести текст обычным способом или через AGG и вернуть BITMAP обратно. При этом можно включить прозрачный фон.

Share this post


Link to post
Share on other sites

Решил вопрос попиксельным копированием (вроде не очень грузит проц.) результатом вполне доволен.

post-1-0-92519300-1314258963_thumb.png

вот код:

// OpenCVFont.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "opencv2/opencv.hpp"
#include <ft2build.h>
#include FT_FREETYPE_H
FT_Library  library;
FT_Face     face;      /* handle to face object */
using namespace cv; // all the new API is put into "cv" namespace. Export its content
using namespace std;
//-----------------------------------------------------------------------
void my_draw_bitmap(Mat& img,FT_Bitmap* bitmap,int x,int y)
{
	for(int i=0;i<bitmap->rows;i++)
	{
	 for(int j=0;j<bitmap->width;j++)
	 {
		 unsigned char val=bitmap->buffer[j+i*bitmap->pitch];
		 if(val!=0)
		 {
			 img.at<Vec3b>(i+y,j+x)=Vec3b(val,val,val);
		 }
	 }	
	}
}
//-----------------------------------------------------------------------
void PrintString(Mat& img,std::wstring str,int x,int y)
{
        FT_Bool       use_kerning=0;
        FT_UInt       previous=0;
        use_kerning = FT_HAS_KERNING( face );
        float prev_yadv=0;
        float posx=0;
        float posy=0;
        float dx=0;
        for(int k=0;k<str.length();k++)
        {
                int glyph_index = FT_Get_Char_Index( face, str.c_str()[k] );
                FT_GlyphSlot  slot = face->glyph;  /* a small shortcut */
                if(k>0){dx=slot->advance.x/64;  }       
                FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
                FT_Render_Glyph (slot,FT_RENDER_MODE_NORMAL);
                prev_yadv=slot->metrics.vertAdvance/64; 
                if ( use_kerning && previous && glyph_index )
                {
                        FT_Vector  delta;
                        FT_Get_Kerning( face, previous, glyph_index, FT_KERNING_DEFAULT, &delta );
                        posx += (delta.x/64);
                }
                posx+=(dx);
                my_draw_bitmap(img,&slot->bitmap,posx+x+ slot->bitmap_left,y - slot->bitmap_top+posy );
                previous = glyph_index;
        }
}
//-----------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
	FT_Init_FreeType( &library );
	FT_New_Face( library,"arial.ttf",0,&face );
	FT_Set_Pixel_Sizes(face,40,0);
	FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	Mat img(480,640,CV_8UC3);
	PrintString(img,L"Привет!",100,100);
	cv::imshow("win",img);
	cv::waitKey(0);
	return 0;
}

 

  • Like 2

Share this post


Link to post
Share on other sites

Добавил многострочный вывод.

post-1-0-21407500-1314271088_thumb.png

// OpenCVFont.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "opencv2/opencv.hpp"
#include <ft2build.h>
#include FT_FREETYPE_H
FT_Library  library;
FT_Face     face;      /* handle to face object */

using namespace cv; // all the new API is put into "cv" namespace. Export its content
using namespace std;
//-----------------------------------------------------------------------
void my_draw_bitmap(Mat& img,FT_Bitmap* bitmap,int x,int y)
{
	for(int i=0;i<bitmap->rows;i++)
	{
	 for(int j=0;j<bitmap->width;j++)
	 {
		 unsigned char val=bitmap->buffer[j+i*bitmap->pitch];
		 if(val!=0)
		 {
			 img.at<Vec3b>(i+y,j+x)=Vec3b(val,val,val);
		 }
	 }	
	}
}
//-----------------------------------------------------------------------
float PrintString(Mat& img,std::wstring str,int x,int y)
{
	FT_Bool       use_kerning=0;
	FT_UInt       previous=0;
	use_kerning = FT_HAS_KERNING( face );
	float prev_yadv=0;
	float posx=0;
	float posy=0;
	float dx=0;
	for(int k=0;k<str.length();k++)
	{
		int glyph_index = FT_Get_Char_Index( face, str.c_str()[k] );
		FT_GlyphSlot  slot = face->glyph;  /* a small shortcut */
		if(k>0){dx=slot->advance.x/64;	}	
		FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
		FT_Render_Glyph (slot,FT_RENDER_MODE_NORMAL);
		prev_yadv=slot->metrics.vertAdvance/64;	
		if ( use_kerning && previous && glyph_index )
		{
			FT_Vector  delta;
			FT_Get_Kerning( face, previous, glyph_index, FT_KERNING_DEFAULT, &delta );
			posx += (delta.x/64);
		}
		posx+=(dx);
		my_draw_bitmap(img,&slot->bitmap,posx+x+ slot->bitmap_left,y - slot->bitmap_top+posy );
		previous = glyph_index;
	}
return prev_yadv;
}
//-----------------------------------------------------------------------
void PrintText(Mat& img,std::wstring str,int x,int y)
{
float posy=0;
	for(int pos=str.find_first_of(L'\n');pos!=wstring::npos;pos=str.find_first_of(L'\n'))
	{
	std::wstring substr=str.substr(0,pos);
	str.erase(0,pos+1);
	posy+=PrintString(img,substr,x,y+posy);
	}
	PrintString(img,str,x,y+posy);
}
//-----------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
	FT_Init_FreeType( &library );
	FT_New_Face( library,"arial.ttf",0,&face );
	FT_Set_Pixel_Sizes(face,12,0);
	FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	Mat img(480,640,CV_8UC3);
std::wstring str= L"Мой дядя самых честных правил,\n\
Когда не в шутку занемог,\n\
Он уважать себя заставил \n\
И лучше выдумать не мог.\n\
Его пример другим наука;\n\
Но, боже мой, какая скука\n\
С больным сидеть и день и ночь,\n\
Не отходя ни шагу прочь!\n\
Какое низкое коварство\n\
Полу-живого забавлять,\n\
Ему подушки поправлять,\n\
Печально подносить лекарство,\n\
Вздыхать и думать про себя:\n\
Когда же чёрт возьмет тебя!\n";
  
	PrintText(img,str,100,50);
	cv::imshow("win",img);
	cv::waitKey(0);
	return 0;
}

С больным сидеть и день и ночь,\n\
Не отходя ни шагу прочь!\n\
Какое низкое коварство\n\
Полу-живого забавлять,\n\
Ему подушки поправлять,\n\
Печально подносить лекарство,\n\
Вздыхать и думать про себя:\n\
Когда же чёрт возьмет тебя!\n";
  
	PrintText(img,str,100,50);
	cv::imshow("win",img);
	cv::waitKey(0);
	return 0;
}

 

Share this post


Link to post
Share on other sites

Данный способ актуален? Или имеются уже варианты по лучше?

Share this post


Link to post
Share on other sites

К сожалению актуален.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.

×