문득 알파 채널이 있는, 투명한 PNG 파일을 합성하는 방법이 궁금해져서 인터넷을 찾아봤습니다. 역시나 잘 정리되어 있는 사이트가 꽤 있더군요.


https://www.learnopencv.com/alpha-blending-using-opencv-cpp-python/
여기는 저도 자주 참고하는 learn opencv 블로그구요.. 일단 PNG 파일을 채널 분리하여 multiply(), add() 함수를 조합해서 합성하는 방법과 직접 픽셀 값에 접근해서 합성하는 방법을 비교해놓았습니다. 픽셀 값 접근할 때 Mat::data 멤버 함수를 직접 참조하는데, 음.. 개인적으로는 좋아하지 않는 방법입니다.


http://jepsonsblog.blogspot.kr/2012/10/overlay-transparent-image-in-opencv.html
큰 배경 영상의 일부분에 작은 크기의 PNG 파일을 합성하는 코드를 잘 만들어둔 블로그입니다. 역시나 data 멤버 변수를 직접 사용했구요..


https://blog.naver.com/h2ohyukhyuk/220935134665
한글로 된 블로그인데, 예제로 사용한 그림이 꽤 재미있었습니다.


일단 위에 나타난 코드와 예제를 참고하여, 이번 포스트에서는 입력 영상에서 얼굴을 검출하고, 검출한 얼굴 위에 고양이 귀를 합성하는 예제를 만들어보았습니다. 일단 전체 코드를 공개하겠습니다.


#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace std;

void overlayImage(Mat& src, const Mat& over, const Point& pos)
{
	CV_Assert(src.type() == CV_8UC3);
	CV_Assert(over.type() == CV_8UC4);

	int sx = std::max(pos.x, 0);
	int sy = std::max(pos.y, 0);
	int ex = std::min(pos.x + over.cols, src.cols);
	int ey = std::min(pos.y + over.rows, src.rows);

	for (int y = sy; y < ey; y++) {
		int y2 = y - pos.y; // y coordinate in overlay image

		Vec3b* pSrc = src.ptr<Vec3b>(y);
		const Vec4b* pOvr = over.ptr<Vec4b>(y2);

		for (int x = sx; x < ex; x++) {
			int x2 = x - pos.x; // x coordinate in overlay image

			float alpha = (float)pOvr[x2][3] / 255.f;

			if (alpha > 0.f) {
				pSrc[x][0] = saturate_cast<uchar>(pSrc[x][0] * (1.f - alpha) 
						+ pOvr[x2][0] * alpha);
				pSrc[x][1] = saturate_cast<uchar>(pSrc[x][1] * (1.f - alpha) 
						+ pOvr[x2][1] * alpha);
				pSrc[x][2] = saturate_cast<uchar>(pSrc[x][2] * (1.f - alpha) 
						+ pOvr[x2][2] * alpha);
			}
		}
	}
}

int main()
{
	Mat src = imread("dlwlrma.jpg", IMREAD_COLOR);
//	Mat src = imread("iujelly.jpg", IMREAD_COLOR);
	Mat cat = imread("cat.png", IMREAD_UNCHANGED);

	if (src.empty() || cat.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	CascadeClassifier face_cascade("haarcascade_frontalface_default.xml");

	if (face_cascade.empty()) {
		cerr << "Failed to open (face) xml file!" << endl;
		return -1;
	}

	vector<Rect> faces;
	face_cascade.detectMultiScale(src, faces);

	for (Rect face : faces) {
		float fx = float(face.width) / cat.cols;

		Mat tmp;
		resize(cat, tmp, Size(), fx, fx);

		Point pos(face.x, face.y - face.height / 3);
		overlayImage(src, tmp, pos);
	}

	imshow("src", src);

	waitKey(0);
	return 0;
}


위 예제에서 overlayImage(src, over, pos); 함수는 3채널 컬러 영상 src에, 4채널 PNG 파일로부터 만들어진 over 영상을 pos 위치에 합성하여 src에 저장합니다. 제가 작성한 overlayImage() 함수가 앞서 소개한 블로그에서 사용한 함수와 다른 점은,

- data 멤버를 직접 참조하는 방법 대신, 좀 더 안전한 Mat::ptr() 함수를 사용하였고..
- for 루프 시작 전에 실제 합성이 이루어지는 영역을 계산하였고..
- 한 장의 입력 영상에 여러 번 합성을 할 수 있도록 구조를 변경했다는 점입니다.


얼굴을 검출하는 코드에 대한 자세한 설명은 생략하구요.. ranged-for 루프에서 합성할 때에는 검출된 얼굴 영역의 가로 크기에 맞게 고양이 귀 영상 크기를 축소한 후, 얼굴 위 적당한 위치에 고양이 귀를 합성합니다. 얼굴이 2개 있으면 2번 합성하게 됩니다. 실제 결과는 아래와 같습니다.




참고로 제 Desktop에서 512x512 영상에 합성하는데 0.1ms도 걸리지 않았습니다. 꽤 빠르네요.


Posted by kkokkal
: