반응형

아마 글꼴을 찾는데 문제가 있을 수 있습니다 "맑은 고딕"은 정상적으로 작동되지만 컴퓨터마다 다를 수 있을 것 같습니다. 될 수도 있고 안 될 수도 있다는 점.. 안된다면.. 폰트 쪽 코드를 수정하면 될지도..?

 

일단 음.. 이걸 성공하게 된건.. 한국어나 영어로 찾아볼 땐 아무리 찾아도 안나왔는데.. 일본에는 전문가들이 많으니깐 있지 않을까해서 일본어로 검색해보니.. 역시 있었다. 그래서 해결하게 됨

 

바로 가져갈 분은 맨 아래쪽에 있어요.

 

https://jitaku.work/it/category/image-processing/opencv/write_japanese/

 

C++のOpenCVで日本語をputTextする

Top 作成日: 2020.06.29 C++のOpenCVで日本語をputTextする Pythonの場合比較的容易にできる、日本語を画面描画する方法。 C++はそういったことができないので非常に困っていた。 で、ネットからパク

jitaku.work

일단 나는 여기 것을 사용했는데. 여기에 있는 코드를 바로 쓰면 당연히 안될꺼다.

HFONT hFont = ::CreateFontA(
        fontSize, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE,
        SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
        VARIABLE_PITCH | FF_ROMAN, fontname);

코드를 보면 이런데.. 저기 아래 SHIFTJIS_CHARSET 저것때문에 안된다. 아마 인코딩 문제 같은데 뭐 저거를 다른 대부분 언어도 되게 할려면 DEFAULT_CHARSET으로 하면 아마 될 것 같다. 당연 한국어도 됨!

 

일단 저걸로 하면 되고

추가적으로 수정한 것

일단 내 프로젝트에선 좀 중요하기 때문에 얘를 그냥 출력하면 안티에일리어싱이 결국엔 안된다.

위의 코드에서 안티에일리어싱을 키면 되는데 킨다 하더라도 코드 때문에 결국은 안된다.

 

일단 이 코드의 방식을 보면 먼저 MFC로 어디선가?에서 적을 OpenCV Mat에서의 가로 길이만큼의 텍스트 이미지를 생성한 후 가져와서 비트맵을 Mat 방식대로 바꿔서 적용하는 방식이다. (아마 맞을꺼임)

 

근데 저기 코드를 대충 보면

if (_tmp[0] == 0 && _tmp[1] == 0 && _tmp[2] == 0) {
                    _img[0] = (unsigned char)color.val[0];
                    _img[1] = (unsigned char)color.val[1];
                    _img[2] = (unsigned char)color.val[2];
                }

이런 코드가 있다. 그리고 위쪽에 보면 back_color로 0x99가 정의 되어 있는데.. 이걸 보면 대충 알 수 있다. 일단 MFC에서 텍스트 이미지를 생성하면 텍스트 밖은 기본적으로 흰색(255, 255, 255), 텍스트는 검은색(0, 0, 0)이다.이다. 또한 0x99는 153으로 RGB 153 153 153 뭐 이래보면 회색이다. 최대가 255이니.. 그래서 최종적으로 저 코드는 텍스트외의 배경 (153, 153, 153)과 (255, 255, 255) 부분을 제외한 검은색 부분(텍스트 부분)을 가져와 사전에 정의한 RGB색대로 다시 설정하는 방식이다.

 

이 때문에 이 코드에선 아무리 안티에일리어싱을 한들.. 안티 에일리어싱이 픽셀 사이사이에 텍스트보다 연한 색을 찍는건데 이 것들이 다 제외되면서 결국엔 안티 에일리어싱이 안찍힌다..

 

그래서 내가 생각한 방식은 안티 에일리어싱의 경우 연한 색도 처리해야하기 때문에 배경색이 바뀔때마다 당연히 글자 색도 변경될테니 배경색을 직접 지정하고 텍스트 색도 직접 지정해서 나중에 배경색만 빼는 것이다. 그러면 결국엔 적용되는건 텍스트의 진한 픽셀부터 연한 픽셀까지이다. 그래서 이 방법으로 코딩한건 뭐 아래에 넣을꺼고

 

또한 내가 지정한 것 중에 다른 것들도 있지만 ori가 있다. 내가 당시 했을때 오리엔테이션인가 뭔가 하는걸로 줄여서 적은 것 같은데.. 잘 못 지정한 것 같다.

어쨌든 저 코드를 보면

int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준이 왼쪽이면 그대로, 중간이면 기준점 - 넓이 / 2, 오른쪽이면 기준점 - 넓이

대충 이렇게 적어놨다. 조건연산자로 이리저리 해놨는데 주석문을 보면 대충 알 수 있다. 0이면 왼쪽 기준 1이면 중간 기준 2이면 오른쪽 기준

말 그대로다. 사전에 정의한 글자 위치대로 0이면 텍스트의 맨 왼쪽 기준으로 텍스트가 생성되고 1이면 텍스트의 중간으로 생성 2이면 텍스트의 오른쪽 끝으로 생성된다.

 

그리고 세로 축도 할려했는데.. 음..! 이 때 당시엔 필요가 없었어서.. 하실 분들은 직접 하시길! 내가 기억하기론 세로는 맨 위쪽 기준으로 되는걸로 알고 있다.

 

코드를 보면 뭐 배경색 칠하는 것도 있고 텍스트 색 지정하는 것도 있고 그렇다.. 어쨌든 그런거임!

 

이제 코드를 보여줌!

코드

void _putText(cv::Mat& img, const cv::String& text, const cv::Point& org, const int ori, const char* fontname, const int fontWeight, const double fontScale, const bool anti, const RGBScale textcolor, const RGBScale bkcolor) {
	int fontSize = (int)(10 * fontScale);
	int width = img.cols;
	int height = fontSize * 3 / 2;

	HDC hdc = CreateCompatibleDC(NULL);

	HBRUSH hBrush = CreateSolidBrush(bkcolor.rgb);

	RECT rect;
	rect.left = rect.top = 0;
	rect.right = width;
	rect.bottom = height;

	BITMAPINFOHEADER header;
	ZeroMemory(&header, sizeof(BITMAPINFOHEADER));
	header.biSize = sizeof(BITMAPINFOHEADER);
	header.biWidth = width;
	header.biHeight = height;
	header.biPlanes = 1;
	header.biBitCount = 24;
	BITMAPINFO bitmapInfo;
	bitmapInfo.bmiHeader = header;
	HBITMAP hbmp = CreateDIBSection(NULL, (LPBITMAPINFO)&bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);
	SelectObject(hdc, hbmp);

	FillRect(hdc, &rect, hBrush);

	BITMAP  bitmap;
	GetObject(hbmp, sizeof(BITMAP), &bitmap);

	HFONT hFont = CreateFontA(
		fontSize,
		0,
		0,
		0,
		fontWeight,
		FALSE,
		FALSE,
		FALSE,
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS, //SHIFTJIS_CHARSET -> DEFAULT_CHARSET으로 변경
		CLIP_DEFAULT_PRECIS,
		(anti ? ANTIALIASED_QUALITY : DEFAULT_QUALITY), //안티를 허용하면 해주고 아니면 안해주고
		VARIABLE_PITCH | FF_ROMAN,
		fontname);
	SelectObject(hdc, hFont);
	SetTextColor(hdc, textcolor.rgb);
	SetBkColor(hdc, bkcolor.rgb);

	//넓이 높이 구하기
	SIZE size;
	GetTextExtentPoint32A(hdc, text.c_str(), (int)text.length(), &size);

	TextOutA(hdc, 0, height / 3 * 1, text.c_str(), (int)text.length());
	int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준이 왼쪽이면 그대로, 중간이면 기준점 - 넓이 / 2, 오른쪽이면 기준점 - 넓이
	int posY = org.y - (size.cy / 2 + 5);

	unsigned char* _tmp;
	unsigned char* _img;
	for (int y = 0; y < bitmap.bmHeight; y++) {
		if (posY + y >= 0 && posY + y < img.rows) {
			_img = img.data + (int)(3 * posX + (posY + y) * (((bitmap.bmBitsPixel / 8) * img.cols) & ~3));
			_tmp = (unsigned char*)(bitmap.bmBits) + (int)((bitmap.bmHeight - y - 1) * (((bitmap.bmBitsPixel / 8) * bitmap.bmWidth) & ~3));
			for (int x = 0; x < bitmap.bmWidth; x++) {
				if (x + posX >= img.cols) {
					break;
				}

				if (_tmp[0] != bkcolor.b || _tmp[1] != bkcolor.g || _tmp[2] != bkcolor.r) { //순서를 bgr를 써서 bgr로 한거, rgb 순서이면 그걸로 해야함
					_img[0] = (unsigned char)_tmp[0];
					_img[1] = (unsigned char)_tmp[1];
					_img[2] = (unsigned char)_tmp[2];
				}
				_img += 3;
				_tmp += 3;
			}
		}
	}

	DeleteObject(hFont);
	DeleteObject(hbmp);
	DeleteDC(hdc);
}

주석문 보고 뭐 대충 하면 될 것 같다. 난 OPENCV의 기본 MAT의 설정인 BGR때문에 아래가 BGR 기준이고 RGB면 순서를 바꿔서 작성해야 할 것이다.

 

cv::Mat& img, const cv::String& text, const cv::Point& org, const int ori, const char* fontname, const int fontWeight, const double fontScale, const bool anti, const RGBScale textcolor, const RGBScale bkcolor

파라미터를 대충 설명하면

  • cv::Mat& img -> 텍스트를 적용할 이미지
  • cv::String& text -> 출력할 텍스트
  • cv::Point& org -> OpenCV 이미지에서 텍스트를 지정할 위치
  • int ori -> 텍스트 위치를 지정할 때 왼쪽 중앙 오른쪽으로 어느 위치로 정렬해서 출력할지
  • char* fontname -> 폰트 이름 (예: 맑은 코딕)
  • int fontWeight -> 글자의 굵기 (예: FW_BOLD 이런거 하면 됨.. 그냥 숫자로 해도 되고)
  • double fontScale -> 글자 크기 (예: 이건 좀 보정을 안했는데 그냥 무조건 큰 숫자 적으면 진짜 커지므로 기본 생각했던 것보다 반 작게 줄여서 적으면 아마 맞을꺼다)
  • bool anti -> 안티에일리어싱을 할꺼냐 말꺼냐
  • RGBScale textcolor -> 텍스트의 컬러 지정 (예: RGBScale(255, 0, 0))
  • RGBScale bkcolor -> 배경색 지정 (예: RGBScale(255, 255, 255))

어쨌든 이정도다.. 뭐 쓰다보니 파라미터가 너무 많아지긴 했지만.. 뭐 내가 쓰는건데 뭐 어때!

_putText(outImg, "나", cvPoint(50, 50), 1, "맑은 고딕", FW_BOLD, 6, true, RGBScale(0, 0, 0), RGBScale(255, 255, 255));

사용 예시는 위와 같다.

 

어쨌든 그렇다. 이거 글 쓰는 것도 힘들 구 생각하는 것도 힘들었는데..

나중에 자기 글에다 쓰고 싶다면... 적어도 링크라도.. 부탁.. (이거 그럼 만든게 2022년 11월 쯤에 만들었으니.. 고등학교 2학년 때 만든거군.. 아 여기 학교 C/C++과목 전혀 없음)

 

뭐 어쨌든 끄읕!

 

얼마나 사용해줄진 모르겠지만.. C++은 겁나 복잡해!!

 

(파이썬은 짧고 쉽던데.. 방식은 이거와 비슷)

 

2023-03-01 추가

코드에서 RGBScale에 대한 구조체 내용이 없어서 다시 정리하고 구조체에 대한 것도 새로 적습니다.

struct RGBScale {
    int r = 0;
    int g = 0;
    int b = 0;
    int rgb = 0;

    RGBScale(int r, int g, int b) {
        this->r = r;
        this->g = g;
        this->b = b;

        this->rgb = RGB(r, g, b);
    }
};

RGBScale(0xFF, 0xFF, 0xFF)로 적으면 되는데 0xFF는 그냥 16진수 이기 때문에 0부터 255 숫자중 원하는 색깔에 맞춰 적으시면 됩니다! 그리고.. 기본적으로 cv::Mat 생성할 때 RGB가 아닌 BGR로 생성하기 때문에 아마 RGB 이미지를 넣어서 적용시키면 색깔이 다르게 나올꺼기 때문에.. 인풋엔 BGR로 넣으시면 됩니다. 수정할려면 아래 적혀있는 코드에서 비트맵을 OpenCV로 바꿔주는 곳에서 b, g, r 적혀있는 것을 r, g, b로 변경하시면 될겁니다. RGBScale를 따로 만든 이유는 그냥 r, g, b도 추출하고 rgb도 출력하고 하기 위함!

 

아래는 다시 정리해본 코드!

void CPputText(cv::Mat& O_image, cv::String text, cv::Point org, int ori, const char* fontName, int fontWeight, double fontScale, RGBScale textColor, RGBScale bkColor) {
	int fontSize = (int)(10 * fontScale);
	int width = O_image.cols;
	int height = fontSize * 3 / 2;

	HDC hdc = CreateCompatibleDC(NULL); //텍스트 이미지를 만들어두는 곳 같은거

	HBRUSH hBrush = CreateSolidBrush(bkColor.rgb); //채우는 방식인데 bkColor로 단색으로 채우는거

	//텍스트 이미지 크기 정하는거
	RECT rect;
	rect.left = rect.top = 0;
	rect.right = width;
	rect.bottom = height;

	//비트맵의 구조를 사전에 정의하는 것 크기나 색
	BITMAPINFOHEADER header;
	ZeroMemory(&header, sizeof(BITMAPINFOHEADER));
	header.biSize = sizeof(BITMAPINFOHEADER);
	header.biWidth = width;
	header.biHeight = height;
	header.biPlanes = 1;
	header.biBitCount = 24;
	BITMAPINFO bitmapInfo;
	bitmapInfo.bmiHeader = header;
	HBITMAP hbmp = CreateDIBSection(NULL, (LPBITMAPINFO)&bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);
	SelectObject(hdc, hbmp); //hdc에 적용? 하는 거

	FillRect(hdc, &rect, hBrush); //지정한 크기만큼 완전하게 채우는거 (다 채움)

	BITMAP bitmap;
	GetObject(hbmp, sizeof(BITMAP), &bitmap);

	//텍스트 이미지 만들 때 사용할 수 있는 폰트를 생성? 하는 그런거
	HFONT hFont = CreateFontA(
		fontSize,
		0,
		0,
		0,
		fontWeight,
		FALSE,
		FALSE,
		FALSE,
		DEFAULT_CHARSET, //한국어나 일본어나 해주게 하는거 (아마)
		OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS,
		ANTIALIASED_QUALITY, //안티 에일리어싱을 켜주는거
		VARIABLE_PITCH | FF_ROMAN,
		fontName);
	SelectObject(hdc, hFont);
	SetTextColor(hdc, textColor.rgb);
	SetBkColor(hdc, bkColor.rgb);

	//계산을 위해 미리 텍스트의 사이즈 구하는거
	SIZE size;
	GetTextExtentPoint32A(hdc, text.c_str(), (int)text.length(), &size);

	TextOutA(hdc, 0, height / 3 * 1, text.c_str(), (int)text.length()); //이미지에 텍스트 적는거
	int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준 정하는거 0은 텍스트의 왼쪽 1은 텍스트의 중간 2는 텍스트의 오른쪽
	int posY = org.y - (size.cy / 2 + 5);

	//비트맵 사진을 OpenCV이미지에 삽입해주는거
	unsigned char* _tmp;
	unsigned char* _img;
	for (int y = 0; y < bitmap.bmHeight; y++) {
		if (posY + y >= 0 && posY + y < O_image.rows) {
			_img = O_image.data + (int)(3 * posX + (posY + y) * (((bitmap.bmBitsPixel / 8) * O_image.cols) & ~3));
			_tmp = (unsigned char*)(bitmap.bmBits) + (int)((bitmap.bmHeight - y - 1) * (((bitmap.bmBitsPixel / 8) * bitmap.bmWidth) & ~3));
			for (int x = 0; x < bitmap.bmWidth; x++) {
				if (x + posX >= O_image.cols)
					break;

				if (_tmp[0] != bkcolor.b || _tmp[1] != bkcolor.g || _tmp[2] != bkcolor.r) { //텍스트 이미지의 배경 컬러는 없애기 위한 것, bgr 순서로 하는 이유는 Mat 이미지를 처음에 만들 때 BGR 순이여서
					_img[0] = (unsigned char)_tmp[0]; //B
					_img[1] = (unsigned char)_tmp[1]; //G
					_img[2] = (unsigned char)_tmp[2]; //R
				}
				_img += 3;
				_tmp += 3;
			}
		}
	}

	//메모리에서 삭제해주는거 이거 안하면 메모리 계속 사용함
	DeleteObject(hBrush);
	DeleteObject(hFont);
	DeleteObject(hbmp);
	DeleteObject(hdc);
}
cv::Mat inputMat(500, 500, CV_8UC3);
CPputText(inputMat, "안녕!", cvPoint(inputMat.cols / 2, inputMat.rows), 1, "맑은 고딕", FW_BOLD, 6, RGBScale(0xFF, 0xFF, 0xFF), RGBScale(0, 0, 0));

아아마 이렇게 하면 500 * 500 으로 이미지를 생성하고 그 중간에 텍스트를 넣는거니 완전 중간은 아니고.. 세로가 좀 아래로 쳐져있긴 하지만.. 저렇게 하면 잘 작동은 합니다!

반응형

+ Recent posts