사전지식
mjpeg은 동영상을 압축하는 표준이지만, 그 내부를 들여다 보면 가장 핵심적인 부분은 사진 압축 방식인 Jpeg이 담당하고 있다. 대표적으로 BeagleBone Black이나 Raspberry Pi에서 많이 사용되고 있는 mjpeg-streamer의 코드를 살펴보면 좀 더 확실해 진다.아래의 코드는 mjpeg-streamer/plugins/inputuvc에 있는 inputuvc.c 파일의 쓰레드 함수의 내용이다. 즉, 항시 실행되면서 uvc 방식의 웹캠 드라이버로 부터 영상을 얻어오고(videoIn 구조체) 해당 영상의 형식이 YUYV방식이면 compress_yuyv_to_jpeg()에 videoIn과 압축된 영상을 저장할 버퍼를 넘겨주는 작업을 항상 반복하게된다.
/****************************************************************************** Description.: this thread worker grabs a frame and copies it to the global buffer Input Value.: unused Return Value: unused, always NULL ******************************************************************************/ void *cam_thread( void *arg ) { ...... /* * If capturing in YUV mode convert to JPEG now. * This compression requires many CPU cycles, so try to avoid YUV format. * Getting JPEGs straight from the webcam, is one of the major advantages of * Linux-UVC compatible devices. */ if (videoIn->formatIn == V4L2_PIX_FMT_YUYV) { DBG("compressing frame\n"); pglobal->size =
compress_yuyv_to_jpeg
(videoIn, pglobal->buf, videoIn->framesizeIn, gquality); } else { DBG("copying frame\n"); pglobal->size = memcpy_picture(pglobal->buf, videoIn->tmpbuffer, videoIn->buf.bytesused); } ....... }
mjpeg-streamer/plugins/inputuvc밑의 jpeg_utils.c 파일내에 있는 실제적인 압축과정을 수행하는 compress_yuyv_to_jpeg()함수이다. 전체적인 흐름은 jpeg압축에 필요한 사전작업을 수행하고 struct vdIn *vd로 부터의 yuyv형태의 영상 버퍼를 rgb형태로 변환하여 ptr에 저장한다. 그리고 ptr 버퍼의 내용을 libjpeg을 이용해서 압축을 수행하는 과정이다.
/******************************************************************************
Description.: yuv2jpeg function is based on compress_yuyv_to_jpeg written by
Gabriel A. Devenyi.
It uses the destination manager implemented above to compress
YUYV data to JPEG. Most other implementations use the
"jpeg_stdio_dest" from libjpeg, which can not store compressed
pictures to memory instead of a file.
Input Value.: video structure from v4l2uvc.c/h, destination buffer and buffersize
the buffer must be large enough, no error/size checking is done!
Return Value: the buffer will contain the compressed data
******************************************************************************/
int compress_yuyv_to_jpeg(struct vdIn *vd, unsigned char *buffer, int size, int quality) {
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
unsigned char *line_buffer, *yuyv;
int z;
static int written;
line_buffer = calloc (vd->width * 3, 1);
yuyv = vd->framebuffer;
cinfo.err = jpeg_std_error (&jerr);
jpeg_create_compress (&cinfo);
/* jpeg_stdio_dest (&cinfo, file); */
dest_buffer(&cinfo, buffer, size, &written);
cinfo.image_width = vd->width;
cinfo.image_height = vd->height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults (&cinfo);
jpeg_set_quality (&cinfo, quality, TRUE);
jpeg_start_compress (&cinfo, TRUE);
z = 0;
while (cinfo.next_scanline < vd->height) {
int x;
unsigned char *ptr = line_buffer;
for (x = 0; x < vd->width; x++) {
int r, g, b;
int y, u, v;
if (!z)
y = yuyv[0] << 8;
else
y = yuyv[2] << 8;
u = yuyv[1] - 128;
v = yuyv[3] - 128;
r = (y + (359 * v)) >> 8;
g = (y - (88 * u) - (183 * v)) >> 8;
b = (y + (454 * u)) >> 8;
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
if (z++) {
z = 0;
yuyv += 4;
}
}
row_pointer[0] = line_buffer;
jpeg_write_scanlines (&cinfo, row_pointer, 1);
}
jpeg_finish_compress (&cinfo);
jpeg_destroy_compress (&cinfo);
free (line_buffer);
return (written);
}
여러 곳에서 보고 되듯이 BeagleBone과 Raspberry Pi 등의 인기있는 개발 보드는 uvc 드라이버에 의해 구동되는 웹캠의 영상을 위와 같은 과정을 통하여 mjpeg 방식으로 압축한다. 하지만, mjpeg의 초당 압축 성능이 5fps정도로 만족 스럽지 않은 것으로 알려져 있다. 대부분의 시간을 잡아 먹는 과정은 YUV4:2:2를 RGB8:8:8로 변환하는 곳으로 알려져 있다.
이와 같은 성능 저하 요인을 개선하는 방법으로 좀더 성능이 좋은 cpu를 사용하거나 yuv를 rgb로 변환하는 하드웨어 모듈을 내부에 가지고 있는 cpu를 사용하는 것이다. 가장 확실한 방법은 mjpeg 압축을 수행하는 모듈이 있는 cpu를 사용하는 것이지만, 이 같은 개선책은 어느 하나 쉬운 접근법이 존재하지 않는다. 이와 같은 이유로 mjpeg을 수행하는 독립된 외부 장치를 구성하는 방법이 될 것 같다. 그 가장 쉬운 방법은 FPGA를 활용하는 것이다. 그 이유는 하드웨어 구성에 있어서 신경쓸 것이 없고 그져 소프트웨어 적인 방법으로 모든 것이 해결 가능하기 때문이다.
앞으로 설명할 내용은 FPGA에서 구현할 mjpeg 압축을 verilog HDL로 코딩하는 과정을 설명하는 것이다. 그 과정은 몇 가지 큰 걸림돌들이 존재하는데 성능을 극대화 하고 소모하는 FPGA의 공간을 줄이기 위해서 부동 소수점 연산의 불가피한 최적화와 이론적인 mjpeg 알고리즘과는 조금 다르게 전개 되는 DCT과정은 이해 하기가 조금 까다롭고 설명이 많이 필요하다. 이후의 내용은 jpeg 압축의 전체적인 설명, 부동 소수점 연산의 최적화, DCT 과정및 최적화, 마지막으로 이 모든 것을 연결하고 그 결과를 살펴보는 과정으로 이루어 질 것이다.
jpeg 압축
jpeg 압축은 기본적으로 손실 압축이다. 손실 압축임에도 불구하고 jpeg압축 사진을 볼때 압축 이전의 영상과 별다른 차이를 느끼지는 못한다. 그 원인은 사람이 별로 느끼지 못하는 성분만 원래의 영상에서 제거하기 때문이다. 달리 말하면 사람이 차이를 느끼지 못하는 영상의 고주파 성분을 DCT(Discrete Cosine Transform) 통해서 구분해 내고 이진화(Quantization)과정을 통하여 제거하기 때문이다. 이와 같은 과정이 Jpeg 압축이 갖는 핵심 기능이다......................