-
사전 정의
-
소스 코드
<Translational ME>
using System;
using System.IO;
namespace MEModule_ORG
{
class Program
{
static void Main(string[] args)
{
string path = "../../../sequence/BasketballDrive_1920x1080_50.yuv";
FileStream fs = new FileStream(path, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
//Sequence size, number of frames
int width = 1920;
int height = 1080;
int frame = 10;
int N = width * height;
byte[] luma = new byte[frame * N];
byte[] y = new byte[N];
byte[] u = new byte[N / 4];
byte[] v = new byte[N / 4];
for (int f = 0; f < frame; f++)
{
for (int n = 0; n < N; n++)
{
y[n] = br.ReadByte();
}
for (int n = 0; n < N / 4; n++)
{
u[n] = br.ReadByte();
}
for (int n = 0; n < N / 4; n++)
{
v[n] = br.ReadByte();
}
for (int n = 0; n < N; n++)
{
int ndx = N * f + n;
luma[ndx] = y[n];
}
}
byte[] result_stream = make_stream(full_search(luma, frame, width, height),
frame, width, height);
br.Close();
fs.Close();
path = "../../../result/result_org_basketdrive_mb32_f10.yuv";
fs = new FileStream(path, FileMode.Create);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(result_stream);
}
private static byte[,,] full_search(byte[] luma, int frame, int width, int height)
{
...
}
private static byte[] make_stream(byte[,,] residual, int frame, int width, int height)
{
byte[] stream = new byte[width * height * (frame - 1)];
int ndx = 0;
for (int f = 0; f < (frame - 1); f++)
{
for (int h = 0; h < height; h++)
{
for (int w = 0; w < width; w++)
{
stream[ndx] = residual[f, w, h];
ndx++;
}
}
}
return stream;
}
private static byte[,] get_frame(byte[] luma, int f, int width, int height)
{
byte[,] g_frame = new byte[width, height];
for (int h = 0; h < height; h++)
{
for (int w = 0; w < width; w++)
{
g_frame[w, h] = luma[(f * (width * height)) + ((h * width) + w)];
}
}
return g_frame;
}
}
}
시퀀스를 불러오고, ME를 수행하고, 바이트 스트림으로 출력시키는 코드이다.
실제 블록 기반의 Translational ME는 full_search() 함수를 통해 수행된다.
이후에 설명할 Affine, Perspective ME 또한 해당 full_search() 함수만 다를 뿐 전체 프로세스는 동일하다.
using System;
using System.IO;
namespace MEModule_ORG
{
class Program
{
static void Main(string[] args)
{
...
}
private static byte[,,] full_search(byte[] luma, int frame, int width, int height)
{
byte[,,] result = new byte[frame - 1, width, height];
ulong[] allResi = new ulong[frame - 1];
int MB = 32; // 매크로블록 사이즈
int SubMB = 16; // Sub 블록 사이즈
int SU = 16; // 매크로블록에서 위아래 얼마나 더 가져올지
int SR = SU * 2 + SubMB; // 서치레인지 사이즈(32x32)
int REST = MB + SU;
int REST_X = REST + ((width - (REST * 2))
- ((int)((width - (REST * 2)) / MB) * MB));
int REST_Y = REST + ((height - (REST * 2))
- ((int)((height - (REST * 2)) / MB) * MB));
for (int f = 0; f < frame - 1; f++)
{
byte[,] curr = get_frame(luma, f + 1, width, height);
byte[,] prev = get_frame(luma, f, width, height);
// 가장자리 처리
// 좌,우,위,아래 16+8 픽셀씩 복사 (처리 안할 것이므로 원본 이미지 복사)
// i : width, j : height
for (int j = 0; j < height; j++)
{
for (int i = 0; i < REST; i++) //좌
{
result[f, i, j] = curr[i, j];
}
for (int i = (width - REST_X); i < width; i++) //우
{
result[f, i, j] = curr[i, j];
}
}
for (int i = 0; i < width; i++)
{
for (int j = 0; j < REST; j++) //위
{
result[f, i, j] = curr[i, j];
}
for (int j = (height - REST_Y); j < height; j++) //아래
{
result[f, i, j] = curr[i, j];
}
}
// 매크로 블록, 서치 레인지 가져오기
// (32+16) X (32+16) , 매크로 블록이 서치레인지 안에서 비교
// 서치하고자하는 부분은 32X32이지만,
// 실제로 매크로 블록이 차지해야만 하는 공간은
// 오른쪽과 아래쪽에 그보다 16만큼 더 필요하다.
byte[,] mb = new byte[MB, MB]; // 16X16
byte[,] sr = new byte[SR + MB, SR + MB];
for (int h = REST; h < height - REST_Y; h += MB)
{
for (int w = REST; w < width - REST_X; w += MB)
{
//매크로 블록
for (int j = 0; j < MB; j++)
{
for (int i = 0; i < MB; i++)
{
mb[i, j] = curr[w + i, h + j];
}
}
// 서치 레인지
// x, y : 위에서 저장한 매크로 블록에 대한 서치레인지의
// 첫번째 픽셀의 width값과 height값
int x = w - SU;
int y = h - SU;
// 서치레인지를 위해 필요한 공간이 48X48이다.
// 프레임 내에서 가로로 48을 가져오기에 부족한 경우,
// 48대신 가져올 수 있는 최대 길이로 서치레인지를 가져온다.
if (width - x < SR + MB && height - y >= SR + MB)
{
for (int j = 0; j < SR + MB; j++)
{
for (int i = 0; i < width - x; i++)
{
sr[i, j] = prev[x + i, y + j];
}
}
}
// 프레임 내에서 세로로 48을 가져오기에 부족한 경우,
// 48대신 가져올 수 있는 최대 길이로 서치레인지를 가져온다.
else if (width - x >= SR + MB && height - y < SR + MB)
{
for (int j = 0; j < height - y; j++)
{
for (int i = 0; i < SR + MB; i++)
{
sr[i, j] = prev[x + i, y + j];
}
}
}
// 프레임 내에서 가로세로 모두 48을 가져오기에 부족한 경우,
// 48대신 가져올 수 있는 최대 길이로 서치레인지를 가져온다.
else if (width - x < SR + MB && height - y < SR + MB)
{
for (int j = 0; j < height - y; j++)
{
for (int i = 0; i < width - x; i++)
{
sr[i, j] = prev[x + i, y + j];
}
}
}
// 프레임 내에서 가로세로 48을 가져오기에 충분한 경우,
// 48X48만큼 가져온다.
else
{
for (int j = 0; j < SR + MB; j++)
{
for (int i = 0; i < SR + MB; i++)
{
sr[i, j] = prev[x + i, y + j];
}
}
}
// SAD (Sum of Absolute Difference)
// 다 빼서(절대값이 된) 더해서 서치한 부분의 원점에 저장.
int[,] sad = new int[SR, SR];
int sum = 0;
// sad 전체를 임의의 큰값인 999로 초기화시킨다.
for (int j = 0; j < SR; j++)
{
for (int i = 0; i < SR; i++)
{
sad[i, j] = 999;
}
}
// 매크로블록이 프레임을 넘어가는 부분은 서치하지 않는다.
// 즉, sad에 999를 저장하기로 한다.
// sr의 width크기
int sr_width_length = sr.GetLength(0);
// sr의 height크기
int sr_height_length = sr.GetLength(1);
// 실제로 서치할 sr width
int VALID_SR_WIDTH = SR - ((SR + MB) - sr_width_length);
// 실제로 서치할 sr height
int VALID_SR_HEIGHT = SR - ((SR + MB) - sr_height_length);
// 서치레인지에 대한것
for (int j = 0; j < VALID_SR_HEIGHT; j++)
{
for (int i = 0; i < VALID_SR_WIDTH; i++)
{
// 매크로 블록에 대한것
for (int l = 0; l < MB; l++)
{
for (int k = 0; k < MB; k++)
{
sum += Math.Abs(sr[k + i, l + j] - mb[k, l]);
}
}
sad[i, j] = sum;
sum = 0;
}
}
// 모션 벡터 구하기
// sad에 저장된 값 중 가장 작은 값 찾기
// 이는 가장 유사한 블록이라 할 수 있음
int min = int.MaxValue;
int mv_x = 0;
int mv_y = 0;
for (int j = 0; j < SR; j++)
{
for (int i = 0; i < SR; i++)
{
if (sad[i, j] < min)
{
min = sad[i, j];
mv_x = x + i;
mv_y = y + j;
}
}
}
// 매크로블록 크기만큼 빼기
for (int j = 0; j < MB; j++)
{
for (int i = 0; i < MB; i++)
{
result[f, w + i, h + j] = (byte)Math.Abs(
prev[mv_x + i, mv_y + j] - curr[w + i, h + j]);
allResi[f] += result[f, w + i, h + j];
}
}
}
}
}
for (int p = 0; p < frame - 1; p++)
{
string line = String.Format("{0} {1}", p, allResi[p]);
Console.WriteLine(line);
}
// 모든 프레임에 대한 연산이 끝난 후 결과 리턴.
return result;
}
private static byte[] make_stream(byte[,,] residual, int frame, int width, int height)
{
...
}
private static byte[,] get_frame(byte[] luma, int f, int width, int height)
{
...
}
}
}
Translational ME는 함수는 위와 같다.
ME 순서를 간단히 나열하면,
1. 프레임 가장자리 패딩 시키기
2. 서치레인지 내에서 가장 유사한 블록 찾기 (SAD 기반)
3. 현재 블록과 유사한 블록 빼서 잔차 블록 만들기