3D 이미지 나뭇결무늬를 제거하자 - 1
이전 볼륨 렌더링 이미지는 나뭇결 무늬가 심했다. 이를 완화시키는 방법을 소개하고 적용해보자.
먼저 interpolation 방법이다. 이산적인 값으로 이루어져 있는 복셀값을 가중치를 이용하여 연속적인 값을 구하는 방법이다.
Volume.cpp
float Volume::GetVoxel(float x, float y, float z)
{
/// tri - linear interploation
int x_minus = x;
int y_minus = y;
int z_minus = z;
int x_plus = x_minus + 1;
int y_plus = y_minus + 1;
int z_plus = z_minus + 1;
float x_weight = x - static_cast<float>(x_minus);
float y_weight = y - static_cast<float>(y_minus);
float z_weight = z - static_cast<float>(z_minus);
unsigned char coord_000 = GetVoxel(x_minus, y_minus, z_minus);
unsigned char coord_100 = GetVoxel(x_plus, y_minus, z_minus);
unsigned char coord_010 = GetVoxel(x_minus, y_plus, z_minus);
unsigned char coord_110 = GetVoxel(x_plus, y_plus, z_minus);
unsigned char coord_001 = GetVoxel(x_minus, y_minus, z_plus);
unsigned char coord_101 = GetVoxel(x_plus, y_minus, z_plus);
unsigned char coord_011 = GetVoxel(x_minus, y_plus, z_plus);
unsigned char coord_111 = GetVoxel(x_plus, y_plus, z_plus);
float inter000_100 = (1.f-x_weight)*coord_000 + x_weight*coord_100;
float inter010_110 = (1.f-x_weight)*coord_010 + x_weight*coord_110;
float inter2d_1 = (1.f-y_weight)*inter000_100 + y_weight*inter010_110;
float inter001_101 = (1.f-x_weight)*coord_001 + x_weight*coord_101;
float inter011_111 = (1.f-x_weight)*coord_011 + x_weight*coord_111;
float inter2d_2 = (1.f-y_weight)*inter001_101 + y_weight*inter011_111;
float inter3d_1 = (1.f - z_weight)*inter2d_1 + z_weight*inter2d_2;
return inter3d_1;
}
이를 적용해 렌더링 해보면 아래 그림과 같다. 이전보다 나아졌다고 볼 수 없다.
나뭇결 무늬를 보다 효과적으로 제거하기 위해 기존의 1d Pallette 대신 2d Pallette 를 적용해보겠다.
기존에는 하나의 intensity 에 하나의 값을 가진 팔렛이었다면 2d Pallette 는 이전 intensity 와 현재 intensity 를 interpolation 하여
보다 경계를 부드럽게 만든 pallette 라고 볼 수 있다.
TransferFunction.cpp
void TransferFunction::SetColorAlpha2DPallette(int start_color[3],
int end_color[3], int start_alpha, int end_alpha)
{
/// 클래스 멤버 변수 m_PalletteC2D[3], m_PalletteA2D 를 미리 선언함.
for(int i=0; i<3; i++)
{
if(!m_PalletteC2D[i])
{
m_PalletteC2D[i] = shared_ptr<float>(new float[MAX_INTENSITY*MAX_INTENSITY]);
memset(m_PalletteC2D[i], 0, sizeof(float)*MAX_INTENSITY*MAX_INTENSITY);
}
}
if(m_PalletteA2D)
{
m_PalletteA2D = shared_ptr<float>(new float[MAX_INTENSITY * MAX_INTENSITY]);
memset(m_PalletteA2D, 0, sizeof(float)*MAX_INTENSITY*MAX_INTENSITY);
}
for(int j=0; j<MAX_INTENSITY; j++)
{
for(int i=0; i<MAX_INTENSITY; i++)
{
float diff = 0.f;
float color[3] = {0.f};
float alpha = 0.f;
if( i > j )
{
diff = i - j;
for(int k=j; k<i; k++)
{
float cur_blue = GetPalletteCValue(0,k);
float cur_green = GetPalletteCValue(1,k);
float cur_red = GetPalletteCValue(2,k);
float cur_alpha = GetPalleteAValue(k);
color[0] += cur_blue;
color[1] += cur_green;
color[2] += cur_red;
alpha += cur_alpha;
}
}
else if(j > i)
{
diff = j - i;
for(int k=i; k< j; k++)
{
float cur_blue = GetPalletteCValue(0,k);
float cur_green = GetPalletteCValue(1,k);
float cur_red = GetPalletteCValue(2,k);
float cur_alpha = GetPalleteAValue(k);
color[0] += cur_blue;
color[1] += cur_green;
color[2] += cur_red;
alpha += cur_alpha;
}
}
else
{
diff = 1.f;
float cur_blue = GetPalletteCValue(0,k);
float cur_green = GetPalletteCValue(1,k);
float cur_red = GetPalletteCValue(2,k);
float cur_alpha = GetPalleteAValue(k);
color[0] = cur_blue;
color[1] = cur_green;
color[2] = cur_red;
alpha = cur_alpha;
}
m_PalletteC2D[0].get()[MAX_INTENSITY*j + i] = color[0] / diff;
m_PalletteC2D[0].get()[MAX_INTENSITY*j + i] = color[0] / diff;
m_PalletteC2D[0].get()[MAX_INTENSITY*j + i] = color[0] / diff;
m_PalletteA2D.get()[MAX_INTENSITY*j + i] = alpha / diff;
}
}
}
pallette 를 모두 만들었으니 2D pallette 를 사용하는 함수를 만들어보자.
매개변수로 2개의 intensity 가 들어오고 2개의 값을 interpolation 해서 가중값을 반환한다.
float TransferFunction::GetPalletteC2DValue(int color, float prev_intensity, float intensity) { if(intensity +1.f>= MAX_INTENSITY) return -1.f; if(prev_intensity +1.f >= MAX_INTENSITY) return -1.f;
float coord_00 = m_PalletteC2D[color].get()[MAX_INTENSITY*static_cast<int>(prev_intensity) + static_cast<int>(intensity)];
float coord_10 = m_PalletteC2D[color].get()[MAX_INTENSITY*static_cast<int>(prev_intensity+1) + static_cast<int>(intensity)];
float coord_01 = m_PalletteC2D[color].get()[MAX_INTENSITY*static_cast<int>(prev_intensity) + static_cast<int>(intensity+1)];
float coord_11 = m_PalletteC2D[color].get()[MAX_INTENSITY*static_cast<int>(prev_intensity+1) + static_cast<int>(intensity+1)];
float weight[2] = {prev_intensity - static_cast<int>(prev_intensity),
intensity - static_cast<int>(intensity)};
float inter_0010 = (1.f - weight[0]) * coord_00 + weight[0]*coord_10;
float inter_0111 = (1.f - weight[0]) * coord_01 + weight[0]*coord_11;
float res = (1.f - weight[1]) * inter_0010 + weight[1] * inter_0111;
return res; }
float TransferFunction::GetPalletteA2DValue(float prev_intensity, float intensity) { if(prev_intensity + 1.f >= MAX_INTENSITY) return -1.f; if(intensity + 1.f >= MAX_INTENSITY) return -1.f;
float coord_00 = m_PalletteA2D.get()[MAX_INTENSITY*static_cast<int>(prev_intensity) + static_cast<int>(intensity)];
float coord_10 = m_PalletteA2D.get()[MAX_INTENSITY*static_cast<int>(prev_intensity+1) + static_cast<int>(intensity)];
float coord_01 = m_PalletteA2D.get()[MAX_INTENSITY*static_cast<int>(prev_intensity) + static_cast<int>(intensity+1)];
float coord_11 = m_PalletteA2D.get()[MAX_INTENSITY*static_cast<int>(prev_intensity+1) + static_cast<int>(intensity+1)];
float weight[2] = {prev_intensity - static_cast<int>(prev_intensity),
intensity - static_cast<int>(intensity)};
float inter0010 = (1.f - weight[0]) * coord_00 + weight[0] * coord_10;
float inter0111 = (1.f - weight[0]) * coord_01 + weight[0] * coord_11;
float res = (1.f - weight[1]) * inter0010 + weight[1]* inter0111;
return res; }
이후 이를 사용하는 부분을 다듬어야 한다.
Renderer.cpp
bool Renderer::RenderVRAnyDirection(unsigned char* image,
const int img_width, cons int img_height, int DirKey)
{
...
/// prev_intensity 를 가져온다.
float prev_intensity = m_pVolume->GetVoxel(adv_coord.x - view_vector.x,
adv_coord.y - view_vector.y, adv_coord.z - view_vector.z);
/// intensity 를 가져온다.
float intensity = m_pVolume->GetVoxel(adv_coord.x, adv_coord.y, adv_coord.z);
float cur_blue = m_pTF->GetPalletteC2DValue(0,prev_intensity, intensity);
float cur_green = m_pTF->GetPalletteC2DValue(1,prev_intensity, intensity);
float cur_red = m_pTF->GetPalletteC2DValue(2,prev_intensity, intensity);
float cur_alpha = m_pTF->GetPalletteA2DValue(prev_intensity, intensity);
...
}
이를 적용하여 volume rendering 하면 아래와 같이 나온다.