Q&A

  • 이미지 합성에 대하여
안녕하세요...

전자앨범을 만드는데 이미지 합성을 하여 가상 사진을 만들려고 합니다. 하지만 어떤 식으로 해야할 지, 또 배경이미지를 원하는 부분만 잘라내는 기능은 있는지? 혹시라도 전자앨범을 만들어 보았다면 많은 조언부탁합니다.

1  COMMENTS
  • Profile
    이정욱 1999.10.21 19:20
    POD의 독자들이 만화 영화를 좋아할지 모르겠다. 만화영화 중에는 '토이스토리' 같이 장면과 캐릭터들이 모두 3차원 오브젝트를 렌더링해 만들어진 멋진 작품도 있지만, 아직까지 대부분 '배경' 그림에 투명한 '셀' 위에 그려진 이미지를 겹쳐서 한프레임 한프레임 찍어서 만들어지고 있다.

    이 기사에서는 어플리케이션에 양념 식으로 간혹 삽입되곤 하는 여러 에니메이션 기법 중 프레임 에니메이션에 대해 살펴보고 그 한 종류인 셀 에니메이션을 구현하는 방법에 대해 생각해 보도록 하겠다.



    프레임 에니메이션.



    Animation은 넓은 의미로 생기, 움직임, 활동을 뜻한다. 즉 멈춰진 그림에 생기를 불어넣어 마치 움직이는 것처럼 만든 것이 에니메이션이다. 대부분 어릴 때, 책장에 연속적인 그림을 그려놓고 빨리 넘기며 움직인다고 신기해 했던 경험을 가지고 있으리라 생각한다. 이렇게 한장 한장이 전체적으로 바뀌면서 움직이는 것처럼 보여주는 것이 프레임 에니메이션이다. 모든 영화 필름의 경우도 이와 같고, 셀 에니메이션 역시 프레임을 얻기위한 기법의 하나이다.

    델파이에서의 프레임 에니메이션은 이미지 리스트를 이용하면 간단히 구현할 수 있다. 프레임 에니메이션을 이용한 AboutBox를 만들어 보자. 이 경우 프레임들이 옆으로(또는 아래로) 길게 연결된 비트맵이 필요하다. 아래 그림은 64X64픽셀의 프레임을 길게 연결해 만들어 둔 비트맵인데, 위 아래에 구멍만 없다 뿐이지 영화 필름과 같다고 생각하면 된다.



    빈 폼을 하나 올려두고 타이머, 이미지 리스트, 스크롤 바, 버튼, 그리고 이미지 컴포넌트를 두개 올려두자. 그리고 각각의 속성을 아래와 같이 맞추어 주자.





    판넬과 라벨 등을 이용해 적당히 화면을 구성해 주도록 한다. 이제 폼의 OnCreate, 타이머의 OnTimer, 스크롤바의 OnChange 이벤트 핸들러를 열고 아래의 코드를 입력하도록 하자.



    ......

    procedure TAboutBox.FormCreate(Sender: TObject);

    begin

    ImageList1.AddMasked(Image1.Picture.Bitmap, Image1.Canvas.Pixels[0,0]);

    ScrollBar1.Max := ImageList1.Count -1

    //Width := 적당한값

    //Height := 마찬가지로 적당한값

    end;



    procedure TAboutBox.Timer1Timer(Sender: TObject);



    begin

    if ScrollBar1.Position = ScrollBar1.Max then ScrollBar1.Position := 0;

    ScrollBar1.Position := ScrollBar1.Position + 1;

    end;



    procedure TAboutBox.ScrollBar1Change(Sender: TObject);

    var

    Bitmap : TBitmap;

    begin

    Bitmap := TBitmap.Create;

    Bitmap.Width := Image2.Width;

    Bitmap.Height := Image2.Height;

    Bitmap.Canvas.Brush.Color := clBtnFace;

    Bitmap.Canvas.Brush.Style := bsSolid;

    Bitmap.Canvas.FillRect(Rect(0,0,Image2.Width,Image2.Height));



    ImageList1.Draw(Bitmap.Canvas, 0, 0, ScrollBar1.Position);

    Image2.Picture.Bitmap.Assign(Bitmap);

    Bitmap.Free;

    end;



    end.



    이 유니트를 About이라는 이름으로 저장하도록 하자. 새로 프로젝트를 시작하고 빈 폼에 버튼을 하나 올려놓은 다음에 OnClick 이벤트를 열어 아래와 같이 입력하자.



    ........

    Implementation



    Uses

    About;



    procedure TForm1.SpeedButton1Click(Sender: TObject);

    var

    AboutBox : TAboutBox;



    begin

    AboutBox := TAboutBox.Create(Self);

    AboutBox.ShowModal;

    AboutBox.Free;

    end;

    ........







    대충 감이 잡히겠지만, 이렇게 프레임의 장면 모두를 연결해서 영화 필름같이 저장해 두는 이런 방식은 몇가지 한계가 있다. 가장 큰 단점은 프레임이 크고 장면 수가 많은 경우에 주체할 수 없이 덩치가 커진다는 것이다. 300X200의 크기를 갖는 에니메이션을 초당 8프레임 정도로 30초간 재생하려면 240장의 화면이 필요하다. 프레임의 색을 256색으로 한다면, 단순히 산술적인 계산으로도

    한 점당 바이트(1) X 가로크기 X 세로크기 X 프레임수 = 15,360,000 바이트.

    약 15메가의 용량이 필요하게 된다. 따라서 위와 같은 형태의 에니메이션은 반복적이고 프레임의 크기가 작은 경우가 아니면 적용하기가 어렵다. 반대로 말하면 위의 AboutBox와 같이 프레임의 크기가 작고 반복적인 경우엔 유용하다는 뜻도 되지만.....



    셀 에니메이션...



    실제로 만화 영화를 만드는 과정을 상상해 보자. 결과적으로 눈에 보이는 화면은 프레임이 되겠지만, 만화 영화를 만드는 과정에서는 프레임을 하나 하나 모두 그려주지는 않는다. 그랬다가는 만화영화란 귀한 사람만 볼 수 있는 보물로 취급 될 것이다. (만화영화 한편당 만들어야 하는 프레임의 갯수는 20만장 이상이라고 한다.)

    만화 영화의 제작은 배경, 원화, 동화 세가지의 과정으로 구분되는데, 먼저 스토리 보드에 따른 배경을 그리고, 20~30프레임 단위로 캐릭터의 동작을 그린 원화를 그리고 나면, 나머지 사이를 동화로 메꾸어 준다. 그렇게 완성된 원화와 동화를 셀 복사기로 셀에 옮기고 물감으로 채색해서 '셀'을 만들고 배경 위에 셀을 겹쳐놓고 한콤마 한콤마 촬영해 프레임을 완성해 나간다.



    만화 영화에서 셀을 이용하는 목적은 결국 완성된 프레임들이 연결된 길다란 필름을 얻기 위해서지만, 위에서 언급 했듯이 그런 식으로 데이터를 저장하는 것은 그다지 현명한 방법이 아니다. 그보다는 셀과 배경을 따로 관리하고, 각 프레임마다의 배경과 그 위에 올라갈 셀의 위치와 크기를 리스트에 저장 해 두는 것이 훨씬 공간을 절약하는 방법이 된다.





    셀에 그려진 이미지와 배경 이미지





    배경 위에 올려놓은 셀



    여기에서 기억해야 할 중요한 얘기는 배경에 투명하게 셀을 올려놓아야 한다는 것인데, 셀의 특정부분을 투명하게 만들어 배경에 겹치는 것은 셀 에니메이션의 가장 기본이 된다. 또한 이렇게 만들어진 합성 배경 위에 여러개의 셀을 다시 겹치더라도 만들어진 배경은 유지 되어야 한다.



    먼저 셀의 위치를 옮기고 다른 셀을 겹침.



    비트맵을 투명하게 만드는 기법에 대해서는 이미 창간호의 "BitBlt를 버리자"라는 기사에서 다룬 적이 있지만, 간단하게나마 다시 원리를 설명하고 배경과 셀을 합성하는 어플리케이션을 만들어 확실하게 이해하도록 하겠다.

    And와 Or연산에 대해서는 잘 알고 있을 것이다. 델파이에서 비트맵 이미지는 Pixels[X,Y]라는 배열의 형태로 표시된다. 이 안에 TColor형의 색깔값이 들어가 이미지를 나타내고 있다. TColor형은 $00000000부터 $00FFFFFF까지의 정수값이다. 8비트씩 각각 256계조의 RGB값을 담고 있고 $00000000이면 흰색, $00FFFFFF이면 검정색, $00FF0000, $0000FF00, $000000FF는 각각 파랑, 녹색, 빨강색을 나타낸다. 이 값들의 연산을 이용해 투명 이미지를 만들게 된다. 즉 '셀'의 투명한 부분을 '투명색'이라는 개념으로 처리하게 된다.



    바탕에 어떤 이미지가 있을 때, 이 이미지에 흰색과 검은색으로 이루어진 이미지를 And연산으로 그리는 경우를 생각해 보자. 배경 이미지가 빨간색이라고 하면 흰색과 만난 부분은



    0000 0000 0000 0000 0000 0000

    And 0000 0000 0000 0000 1111 1111



    _____________________________________________



    0000 0000 0000 0000 1111 1111



    그대로 빨간색으로 남게 된다. 그러나 검정색과 만난 부분은



    1111 1111 1111 1111 1111 1111

    And 0000 0000 0000 0000 1111 1111



    _____________________________________________



    1111 1111 1111 1111 1111 1111



    만나는 색깔이 어떤 색이건 검정색으로 남게 된다. 이 원리에 의해 원래 배경 이미지에 검정색 구멍이 뚫린 형상을 얻는다. 이 과정을 위해 올려두고자 하는 이미지에서 투명부분은 희고 다른 부분은 검게 처리한 이미지가 필요한데, 이 이미지를 "마스크 이미지"라고 부른다. 이렇게 만든 구멍뚫린 배경에 이번엔 투명부분은 검고 다른 부분은 색을 그대로 가진 그림을 Or 연산으로 그려주면, 처음에 원했던 대로 '셀'이 '배경'에 투명하게 올라앉은 결과 화면을 얻게 된다.



    배경에 셀을 투명하게 올리는 방법은 이것이 전부이다. 이 방법으로 전체 에니메이션에서 반복적으로 사용되는 배경과 셀을 프레임의 길이에 관계없이 사용할 수 있고, 저장할 때도 배경과 비트맵, 그리고 프레임의 구성관계만을 저장해 에니메이션을 구성할 수 있다.



    마스크 이미지와 OR이미지 만들고 배경에 합성하기.



    위에서 언급한 이유로, 셀 이미지에서 투명색이 결정되면 이 투명색을 근거로 두가지 이미지를 만들어야 한다. 하나는 투명한 부분은 희고 다른 부분은 모두 검은 '마스크 이미지'란 녀석이고, 또하나는 마스크 이미지에 의해 뚫린 구멍에 올라앉게 될 'OR 이미지'이다. 'OR 이미지'에서 투명한 부분은 모두 검게 표시되어야 한다.



    원본 이미지





    마스크 이미지와 OR 이미지



    빈 폼을 하나 열고 이미지 네개와 버튼 여섯개, 스크롤박스, 그리고 열기 대화상자 하나를 준비하자. 이미지의 이름은 각각 SrcImage, MaskImage, OrImage, BgImage라고 붙여주자.







    BgImage와 SrcImage에 '배경' 버튼과 '셀이미지' 버튼이 눌렸을 때, 적당한 비트맵을 더하도록 한다.



    procedure TForm1.SrcOpenBtnClick(Sender: TObject);

    begin



    if OpenDialog1.Execute then

    SrcImage.Picture.Bitmap.LoadFromFile(OpenDialog1.FileName);

    end;



    procedure TForm1.BgMakeBtnClick(Sender: TObject);

    begin

    if OpenDialog1.Execute then

    BgImage.Picture.Bitmap.LoadFromFile(OpenDialog1.FileName);

    end;



    마스크 이미지를 만드는 법은 아주 간단하다. 비트맵에는 Monochrome이란 프로퍼티가 있는데, 비트맵의 브러시 칼라를 투명색으로 지정하고 이 속성을 켜주면 마스크 이미지를 만들 수 있다. 아래의 소스는 SrcImage에 입력된 원본 이미지를 마스크로 바꾸어 MaskImage라는 TImage컴포넌트에 더해 준다.



    procedure TForm1.MaskMakeBtnClick(Sender: TObject);

    var

    Bmp : TBitmap;

    begin

    Bmp := TBitmap.Create;

    Bmp.Assign(SrcImage.Picture.Bitmap);

    Bmp.Canvas.Brush.Color := Bmp.Canvas.Pixels[0,0]; // 투명색

    Bmp.Monochrome := True;

    MaskImage.Picture.Bitmap.Assign(Bmp);

    Bmp.Free;

    end;



    한가지 문제는 위의 소스가 델파이 3.0에서는 제대로 작동하지 않는다는 것인데, 마스크 이미지가 지정한 브러시 칼라에 대해 제대로 만들어지지 못하고 멋대로 바뀌어 버린다. 물론 델파이 1.0과 2.0에서는 제대로 작동된다. 그 이유는 잘 모르겠지만, 내부적으로 비트맵을 다루는 방식에 변화가 있었던 것은 아닐까 생각된다. 이 부분에 대해서는 알아내는 대로 다시 한번 지면을 만들도록 하겠다.



    OR 이미지를 만드는 것은 상대적으로 조금 복잡하다. 창간호를 자세히 읽은 독자라면 잘 알고 있겠지만, 이 경우 유용한 메소드는 BrushCopy()라는 녀석이다. 어떤 색깔을 전체적으로 원하는 다른 색깔로 바꿀때 아주 멋진 성능을 발휘한다. 하지만 내부적으로 StretchBlt()라는 API가 두번 쓰여서인지 일반적인 BitBlt()이나 Draw() 같은 함수에 비해 상대적으로 매우 느리다. 메모리 요구량을 줄이려면 실행 중에 원본 이미지를 가지고 계속 마스크 이미지와 OR이미지를 만들어야 겠지만, 그보다는 처음에 원본 이미지가 불려졌을 때 이 두 이미지를 미리 만들어 두는 방법을 권한다. 여러번 테스트 해 본 결과, 실행 중에 이 두 이미지를 생성하는 방법은 못써먹을 것이란 결론을 내렸다.



    아래 소스는 원본 셀 이미지에 입력된 비트맵을 OR이미지로 바꾸어 준다.



    procedure TForm1.OrMakeBtnClick(Sender: TObject);

    var

    Bmp : TBitmap;

    begin

    Bmp := TBitmap.Create;

    Bmp.Assign(SrcImage.Picture.Bitmap);

    Bmp.Canvas.Brush.Color := clBlack;

    Bmp.Canvas.BrushCopy(

    Rect(0,0,Bmp.Width,Bmp.Height),

    SrcImage.Picture.Bitmap,

    Rect(0,0,Bmp.Width,Bmp.Height),

    SrcImage.Picture.Bitmap.Canvas.Pixels[0,0]

    );

    OrImage.Picture.Bitmap.Assign(Bmp);



    Bmp.Free;

    end;



    이제 준비된 마스크 이미지와 OR 이미지를 배경에 합성할 차례이다. 델파이는 TCanvas 객체에 CopyMode라는 멋진 프로퍼티를 제공하고 있다. 이 프로퍼티의 기본값은 cmSrcCopy인데, 이경우 이미지는 아무런 연산 없이 그대로 전송된다. And 연산으로 전송 하려면 cmSrcAnd를, OR연산으로 전송 하려면 cmSrcPaint를 사용하면 된다.

    아래의 소스를 보자.



    // 마스크 이미지를 배경에 올리는 부분.

    procedure TForm1.MergeMaskBtnClick(Sender: TObject);

    var



    Bmp : TBitmap;

    begin

    Bmp := TBitmap.Create;

    Bmp.Assign(BgImage.Picture.Bitmap);

    // 전송모드를 AND연산으로...

    Bmp.Canvas.CopyMode := cmSrcAnd;

    Bmp.Canvas.Draw(

    XSpin.Value,

    YSpin.Value,

    MaskImage.Picture.Bitmap

    );

    BgImage.Picture.Bitmap.Assign(Bmp);

    Bmp.Free;

    end;



    // OR 이미지를 배경에 올리는 부분.

    procedure TForm1.MergeORBtnClick(Sender: TObject);

    var

    Bmp : TBitmap;

    begin

    Bmp := TBitmap.Create;



    Bmp.Assign(BgImage.Picture.Bitmap);

    // 전송모드를 OR로...

    Bmp.Canvas.CopyMode := cmSrcPaint;

    Bmp.Canvas.Draw(

    XSpin.Value,

    YSpin.Value,

    OrImage.Picture.Bitmap

    );

    BgImage.Picture.Bitmap.Assign(Bmp);

    Bmp.Free;

    end;



    이해를 돕기위해 두부분으로 분리해 두었다. 실제로 사용할 때는 이 두 부분이 연속적으로 진행되어야 한다.



    여기까지 프레임 에니메이션의 개념과, 프레임 에니메이션을 구현하는 한가지 방법으로써의 셀 에니메이션에 대해 생각해 보았다. 또한 가장 기본이 되는 투명한 셀 이미지를 배경에 올리는 방법에 대해서도 알아 보았다. 다음달에는 셀 에니메이션의 정보를 저장할 자료 구조와 스크립트 형태로 진행되는 프레임 에니메이션에 대한 제안, 그리고 스크립트를 구축해 주는 스크립터라는 프로그램에 대한 소개를 하도록 하겠다.



    이 기사의 진행에 사용된 그림들이 일본 유명 에니메이션의 캐릭터라는 것이 독자들 마음을 아프게 했으면 한다. 필자는 '한국 에니메이션의 자존심'이라는 '아마게돈'을 극장에서 치를 떨며 보았던 많은 만화영화광 중 한명이다. 이 기사에 등장한 '에~모'란 만화는 제작비 절감을 위해서인지 다시 사용한 셀이 많기로 악명이 높은 작품이다. 그런 완성도 면에선 틀림없이 '아마게돈'에 후한 점수를 주어야 한다. 하지만 '아마게돈'은 망하고 이 만화영화는 말 그대로 '떳다'. 아마게돈의 캐릭터의 움직임은 뻣뻣하고, 색깔은 온통 보라색이고, 성우로 기용된 '이~모' 탈렌트의 목소리는 혼자 놀았다. 필자는 그 이유가 '감각의 부재'와 '상상력의 제한'에 있다고 본다. 또한 그렇게 된 이유는 작가의 창작물이 독자에게 평가받지 못하고 법에 의해 단죄되는 황당한 사회현상 덕분이라고 감히 생각한다.



    구성원의 상상력을 제한하는 힘은 어떤 것이건 매우 위험하며 또한 파시즘의 전조라는 옛 지인의 말을 떠올리며 글을 맺는다.





    파워러브 델파이 97년 8월호!!!



    고혜정 wrote:

    > 안녕하세요...

    > 전자앨범을 만드는데 이미지 합성을 하여 가상 사진을 만들려고 합니다. 하지만 어떤 식으로 해야할 지, 또 배경이미지를 원하는 부분만 잘라내는 기능은 있는지? 혹시라도 전자앨범을 만들어 보았다면 많은 조언부탁합니다.