Перейти к содержимому
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;
}

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

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

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

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;
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×