본문 바로가기
프로그래밍 언어/C#

[C#] 이미지 투명화(배경제거) - #2

by Jinwood 2021. 4. 14.
반응형

1편에서는 이미지를 Bitmap으로 변환한 뒤 MakeTransparent(Color) 메서드를 이용해서 배경을 제거하는, Transparent로 변경하는 방법을 설명하였다. 

 

2021.04.13 - [프로그래밍 언어/C#] - [C#] 이미지 투명화(배경제거) - #1

 

[C#] 이미지 투명화(배경제거) - #1

이미지를 투명화 하기 위해서 가장 먼저 어떤 색상을 투명하게 만들 것인지 설정해야 한다. OpenCV를 사용하지 않고 이미지를 편집하기 위해서는 이미지를 Bitmap으로 생성하고, 생성된 Bitmap을 이

hvyair.tistory.com

 

1편에서 설명된 방식으로 배경 제거를 진행했을 경우 입력한 픽셀의 색상만 제거하기 때문에 R,G,B 값이 조금만 다르더라도 해당 색은 제거되지 않는다. 예를 들어 RGB가 (0,0,0)인 색상을 제거하려는 경우 (1,0,0)인 색상은 육안으로 보기엔 같은 색으로 보이겠지만 R, G, B 값이 완벽하게 일치하지 않기 때문에 제거되지 않는 것이다. R, G, B는 각각 0~255까지 값을 가지기 때문에 색의 범위는 매우 다양하다. 

 

따라서 보다 좋은 결과를 얻기 위해서는 Threshold 값을 설정해두고, 각 픽셀의 RGB 값과 제거하려는 픽셀의 RGB 값을 비교해서 Threshold 값 이내라면 제거하는 방식을 추천한다. 이 방식을 진행하기 위해선 비트맵의 각 픽셀에 대한 RGB 값을 비교해야 하므로 반복문이 필수적인데, 구글링 해보면 보통 두 가지로 나뉜다.

 

  1. 이중 for문으로 비트맵의 너비만큼 반복. GetPixel()로 픽셀의 RGB 값 확인. SetPixel()로 해당 픽셀을 설정.
  2. LockBits 를 사용하여 열 단위 픽셀 설정.

1번 방법보다는 2번 방법을 추천한다. 코드를 작성해보면 알겠지만 LockBits를 사용한 방법이 훨씬 빠르다. 실제로 GetPixel()과 SetPixel()을 사용하면 매우 느리다는 검색 결과들이 많이 존재한다.

 

따라서 LockBits를 사용한 방법을 진행할 텐데, 이 중에서도 포인터를 사용하는 방법으로 설명할 것이다. C#에서 포인터를 사용하기 위해서는 unsafe를 사용해야 한다. unsafe를 사용하기 위해서는 프로젝트 설정 -> 빌드 -> 안전하지 않은 코드 허용을 체크해주어야 한다는 것을 잊지 말자.

 

이미지는 1편에서 사용된 이미지를 그대로 사용하였고, threshold 값은 100, 제거할 색상은 1편에서와 같은 검은색이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.Drawing.Imaging;
private unsafe void MakeTransparentByThreshold()
{
    // 비트맵 불러온 뒤 LockBits로 비트맵 데이터 설정
    Bitmap bmp = new Bitmap(@"C:\Users\admin\Desktop\Rhino.PNG");
    BitmapData data = bmp.LockBits(new Rectangle(00, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
 
    // 제거할 색상, 변경될 색상, Threshold 설정
    Color targetColor = Color.Black;
    int changeColorInt = Color.Transparent.ToArgb();
    double threshold = 100;
 
    // 포인터를 이용한 반복문 시작
    int* length = (int*)data.Scan0 + bmp.Height * bmp.Width;
    for(int* p = (int*)data.Scan0; p < length; p++)
    {
        // 픽셀 색상 가져오기
        Color pixelColor = Color.FromArgb(*p);
 
        // 픽셀 색상<->제거할 색상 비교
        double rSquare = Math.Pow((double)(pixelColor.R - targetColor.R), 2.0);
        double gSquare = Math.Pow((double)(pixelColor.G - targetColor.G), 2.0);
        double bSquare = Math.Pow((double)(pixelColor.B - targetColor.B), 2.0);
        double deltaColor = Math.Sqrt(rSquare + gSquare + bSquare);
 
        // 허용값 이내라면 해당 픽셀 색상 변경
        if (deltaColor <= threshold)
        { *= changeColorInt; }
    }
    
    // UnlockBits 설정, 비트맵 저장, 리소스 
    bmp.UnlockBits(data);
    bmp.Save(@"C:\Users\admin\Desktop\Rhino_Transparent.PNG");
    bmp.Dispose();
}
cs

 

위 코드에서 픽셀 색상 <->제거할 색상 비교 부분은 3차원 평면에서 두 점간 거리를 구하는 것과 같다. 배경 제거 결과를 아래에 표시해 두었다. 오른쪽 이미지를 보면 아직 검은색 윤곽이 그대로 남아있지만 1편에서 MakerTransparent()를 사용한 결과보다 훨씬 깔끔하다는 것을 알 수 있다. Threshold 값을 높인다면 더욱 향상된 결과를 확인할 수 있다.

 

배경 제거 결과(좌 : 기존 이미지 / 우 : 배경 제거된 이미지)

 

반응형

댓글