< 현재 상태>


"exp_1" branch를 "master" branch로 병합하려면 먼저 "master" branch로 체크아웃한 후 "git merge exp_1"을 하면 branch가 병합된다.



merge 된 것을 알 수 있다. => "parent" 가 "5" 버전이랑 "exp_1" branch 였던 "3" 버전이 된다. 즉, 두 개의 부모를 갖는 하나의 새로운 커밋이 생성된다.



"exp_1"로 체크아웃한 다음에 master을 병합했다. 그 결과 HEAD를 보면 exp_1과 master가 같은 것을 볼 수 있다.



master와 exp_1 branch가 같아졌기 때문에 "git branch -d exp_1" 명령어로 exp_1 branch를 지었다.



< 병합 방식 >

병합 방식에는 "fast-forward" 방식과 "fast-forward"가 아닌 방식이 있다.

위 그림을 보면 "master" branch는 현재 c2 버전이고 "hotfix"는 c4 버전, "iss53"은 c3 버전이다.


"master"로 체크아웃한 후 "hotfix"를 병합했다. "Fast-forward" 방식으로 merge 된 것을 볼 수 있다.


merge 하기 전 "hotfix" branch의 버전 "c4"가 "master" branch의 버전 "c2"의 내용을 이미 다 알기 때문에 "master" branch는 merge 할 때 단순히 c4 버전을 가리키기만 하면 된다. 이런 방식을 fast-forward(빨리 감기) 방식이라고 하며 별도의 커밋을 생성하지 않고 마스터가 가리키는 곳을 바꾼다.


"master"로 체크아웃한 후 "iss53" branch와 merge 했다. 보면 "fast-forward" 방시이 아닌 것을 알 수 있다.


merge 되기 전에 "master"은 c4 버전으로 c5버전에서 갖지 않는 부분을 가지고 있다. "iss53" 또한 "master"의 c4 버전에 없는 것을 가지고 있기에 새로운 커밋 c6를 만들어서 서로를 merge 해야 한다. 그래서 "master" branch가 c6 버전을 가지게 되고 c4와 c5에서 커밋되었다는 정보를 가지고 있다.(parent) 위 명령어 부분을 보면 'recursive' strategy라고 되어 있다. 해당 방식은 branch들의 공통 조상을 찾고(c2를 찾고) 3-way-merge라는 내부적인 방법으로 c4와 c5를 합쳐 별도의 커밋을 만드는 방식이다.


*git checkout -b "브랜치 명" : branch를 만든 후 해당 branch로 바로 체크아웃하는 명령어

'Git(생활코딩)' 카테고리의 다른 글

Git - branch 병합 시 충돌 해결  (0) 2017.06.15
Git 혁신(2) - branch 정보 확인  (0) 2017.06.13
Git 혁신(1) - branch  (0) 2017.06.12
Git 원리(2) - commit  (0) 2017.06.11
Git 원리(1) - git add  (0) 2017.06.09

해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.



※ 컬러 영상 분할

회색조 영상 분할은 쉽게 구현할 수 있다. 컬러 영상은 앞 포스트에서 배운 배경 분리를 이용한 분할을 사용해도 되지만 배경이 없다면 영상 분할이 어렵다. 컬러 영상을 회색조 영상으로 변환 후 회색조 영상 분할을 할 수 도 있다 .하지만 다음 그림과 같이 제대로 분할이 안 된다.

따라서, 컬러 영상을 쉽게 분할하기 위해서 K-평균 군집화 알고리즘을 사용해야 한다. 회색조 영상의 경우 밝기를 1차원 공간에 점으로 표현이 가능해 값을 기준으로 대소 비교를 해 분할이 가능하다. 컬러 영상의 경우 3차원 공간상에 표현 되기 때문에  점이 아닌 면을 기준으로 분할해야 한다. 이 때 기준에 해당하는 면의 정의가 중요한데 K-평균 군집화 알고리즘을 사용하면 쉽게 면의 정의가 가능하다. 

K-평균 군집화 알고리즘은 고차원 공간에서 데이터를 분할하는 데 유용한 방법이다. 이미 분류된 데이터들에 대한 평균 위치로부터의 거리를 기준으로 새로운 분할을 수행하는 기법이다. "K"는 분류된 영역의 개수를 나타내는 데 K-평균 균집화는 K개의 평균값을 기준으로 데이터를 군집화한다는 의미가 된다. 

먼저 3차원 색 공간에 K-평균 군집화을 이용하기 위해 기준이 되는 영역 "K" 여러 개를 임의로 정의한다. 영역 "K"는 RGB 색공간 내 (R, G, B) 값이다. 영상 내  영상의 모든 픽셀에 대해서 여러 개의 K 영역 중에 어느 곳에 속하는 지 분류한다. 분류 방법은 기준이 되는 (R,G,B)까지의 거리를 계산해 가장 가까운 곳에 분류한다. 분류 후 K개로 군집화된 픽셀 rgb 값들의 평균을 구하고 그 평균을 기준의 되는 영역 "K"로 설정한다. 그리고 다시 모든 픽셀을 K개의 영역으로 분류한다. 이 과정을 계속 반복하다가 새로은 K 영역에서 분류된 픽셀들이 분류되기 전과 변화가 없다면 제대로 K개의 영역으로 분류가 되 영상 분할이 완료된다. 이 과정은 이전 포스트에서 배웠던 이진화를 이용한 영상 분할에서 자동으로 문턱값을 조절하던 순서도와 비슷하다. 다음은 컬러 영상 분할을 위한 순서도다.  


다음은 이를 구현한 소스코드이다.

void KMeansSegmentation(const CByteImage& imageIn, CByteImage& imageOut, int nCluster, BYTE meanR[], BYTE meanG[], BYTE meanB[])
{
	int nWidth  = imageIn.GetWidth();
	int nHeight = imageIn.GetHeight();

	CByteImage imageK = CByteImage(nWidth, nHeight);

	bool bChanged = true;

	int cR, cG, cB, dR, dG, dB, dd, minD;
	int newK;

	unsigned int nSumR[MAX_CLUSTER]; // 각 영역 R 채널 합
	unsigned int nSumG[MAX_CLUSTER]; // 각 영역 G 채널 합
	unsigned int nSumB[MAX_CLUSTER]; // 각 영역 B 채널 합
	unsigned int nNumP[MAX_CLUSTER]; // 각 영역 픽셀 수

	while (bChanged)
	{
		bChanged = false;

		memset(nSumB, 0, sizeof(int)*nCluster); // B채널 합 초기화
		memset(nSumG, 0, sizeof(int)*nCluster); // G채널 합 초기화
		memset(nSumR, 0, sizeof(int)*nCluster); // R채널 합 초기화
		memset(nNumP,  0, sizeof(int)*nCluster); // 픽셀 수 초기화
		
		for (int r=0 ; r<nHeight ; r++)
		{
			BYTE *pIn	= imageIn.GetPtr(r);
			BYTE *pK	= imageK.GetPtr(r);

			int pos = 0;
			for (int c=0 ; c<nWidth ; c++)
			{
				// 현재 위치의 픽셀 값
				cB = pIn[pos++];
				cG = pIn[pos++];
				cR = pIn[pos++];

				// 가장 가까운 분류 탐색 (m_imageK 갱신)
				minD = INT_MAX;
				newK = 0;
				for (int k=0 ; k<nCluster ; k++)
				{
					dB = cB - meanB[k];
					dG = cG - meanG[k];
					dR = cR - meanR[k];
					dd = dB*dB + dG*dG + dR*dR;

					if (dd < minD)
					{
						minD = dd;
						newK = k;
					}
				}

				if (newK != pK[c])
					bChanged = true;

				pK[c] = newK; // 가장 가까운 값으로 변경

				// 평균을 구하기 위한 합 계산
				nSumB[newK] += cB;
				nSumG[newK] += cG;
				nSumR[newK] += cR;
				nNumP[newK]++;
			}
		}

		// 새로운 평균 계산 (m_meanC 갱신)
		for (int k=0 ; k<nCluster ; k++)
		{
			if (nNumP[k])
			{
				meanB[k] = nSumB[k] / (double)nNumP[k];
				meanG[k] = nSumG[k] / (double)nNumP[k];
				meanR[k] = nSumR[k] / (double)nNumP[k];
			}
			else
			{
				meanB[k] = 0;
				meanG[k] = 0;
				meanR[k] = 0;
			}
		}
	} // while (bChanged)

	// 결과 영상 생성
	int curK;
	for (int r=0 ; r<nHeight ; r++)
	{
		BYTE* pOut	= imageOut.GetPtr(r);
		BYTE* pK	= imageK.GetPtr(r);

		int pos = 0;
		for (int c=0 ; c<nWidth ; c++)
		{
			curK = pK[c];
			pOut[pos++] = meanB[curK];
			pOut[pos++] = meanG[curK];
			pOut[pos++] = meanR[curK];
		}
	}
}

매개 변수 meanR[], meanG[], meanB[] 가 기준이 되는 "K" 이다. 예를 들어 meanR[2] = {0, 255}, meanG[2] = {0, 255}, meanB[2] = {0, 255} 라면 RGB(0.0.0), RGB(255,255,255) 를 초기 기준값으로 정하고 nCluster 값이 2가 된다. 각 영역 R,G,B의 채널 합 변수 nSumR/G/B/ 변수는 unsigned int 형으로 선언되었다. unsigned int 는 0xffffffff(4,294,967,295)까지 나타 낼 수 있어 최대 16,843,009(0xffffffff / 255) 개 픽셀의 채널 값 합을 구할 수 있다. 이는 가로 4000 * 3000 픽셀 크기 영상보다 큰 크기이다. 만약 영상의 크기가 더 크다면 unsigned long long int 형으로 선언해도 된다.

위 메서드는 초기 평균값 설정이 수행 속도에 영향을 많이 미친다. 초기 평균값이 실제 분할된 각 영역의 평균값에 최대한 가까울수록 좋다. 그러나 각 영역의 평균값은 알 수 없으므로 각 영역에서 임의의 픽셀을 선택하여 선택된 픽셀의 색상 값을 초기 평균값으로 사용하면 좋은 결과를 얻을 수 있다.

+ Recent posts