Genius DM

Image processing > Add a bitmap header to an image buffer. 본문

.NET

Image processing > Add a bitmap header to an image buffer.

Damon Jung 2018. 8. 14. 00:04

이미지 버퍼에 비트맵 헤더 추가하기


TCP, RTSP 등의 IP 카메라나, USB 인터페이스를 사용하는 Webcam 등으로부터 이미지 버퍼를 받고, BMP, JPG, PNG 등으로 변환하는 것은 이미지 프로세싱에서 매우 흔하면서도, 기본적인 작업이다. 그러나 각 파일 구조에 대한 이해가 부족하면, 이를 처리할 수 없는데 스택오버플로우에 올라온 이런 질문이 바로 적당한 예이다.


카메라에서 받는 바이트 배열 ( 버퍼 ) 가 있는데, 어떻게 비트맵으로 변환하나요?

예제를 찾아봐도 C++ 예제 뿐이고, 제 코드에서는 변환하려고 하면 ParameterException 떨어집니다.

어떻게 하나요?


방법은 BMP 파일 구조만 파악하면 간단하다. https://en.wikipedia.org/wiki/BMP_file_format#Example_1 이곳에 보면 아래 테이블이 제공되며 이 테이블은 이런 2x2 짜리 이미지를 구성하는 Bitmap 자료 구조에 대한 명세이다.





OffsetSizeHex ValueValueDescription
BMP Header
0h242 4D"BM"ID field (42h, 4Dh)
2h446 00 00 0070 bytes (54+16)Size of the BMP file
6h200 00UnusedApplication specific
8h200 00UnusedApplication specific
Ah436 00 00 0054 bytes (14+40)Offset where the pixel array (bitmap data) can be found
DIB Header
Eh428 00 00 0040 bytesNumber of bytes in the DIB header (from this point)
12h402 00 00 002 pixels (left to right order)Width of the bitmap in pixels
16h402 00 00 002 pixels (bottom to top order)Height of the bitmap in pixels. Positive for bottom to top pixel order.
1Ah201 001 planeNumber of color planes being used
1Ch218 0024 bitsNumber of bits per pixel
1Eh400 00 00 000BI_RGB, no pixel array compression used
22h410 00 00 0016 bytesSize of the raw bitmap data (including padding)
26h413 0B 00 002835 pixels/metre horizontalPrint resolution of the image,
72 DPI × 39.3701 inches per metre yields 2834.6472
2Ah413 0B 00 002835 pixels/metre vertical
2Eh400 00 00 000 colorsNumber of colors in the palette
32h400 00 00 000 important colors0 means all colors are important
Start of pixel array (bitmap data)
36h300 00 FF0 0 255Red, Pixel (0,1)
39h3FF FF FF255 255 255White, Pixel (1,1)
3Ch200 000 0Padding for 4 byte alignment (could be a value other than zero)
3Eh3FF 00 00255 0 0Blue, Pixel (0,0)
41h300 FF 000 255 0Green, Pixel (1,0)
44h200 000 0Padding for 4 byte alignment (could be a value other than zero)


BMP Header

    • Offset 0~1 에 각각 0x42, 0x4D Hex 벨류가 들어간다.
    • Offset 2~6 에 BMP 파일 사이즈가 들어간다. BMP Header + DIB Header 사이즈는 총 54이고, 현재 샘플의 비트맵 버퍼 사이즈는 16 이므로 총 54 + 16 이 된다. 이를 Hex 값으로 표현하여 배열에 삽입하게 되면 46 00 00 00 이 된다. 46을 10진수로 변환하면 70 이다.
    • Offset 6~8 은 어플리케이션 세팅에 따라 다르므로 00, 00 을 넣어준다.
    • Offset 8~10 은 어플리케이션 세팅에 따라 다르므로 00, 00 을 넣어준다.
    • Offset 10~14 는 픽셀 데이터가 시작되는 Offset 을 입력한다. 당연히 Header 사이즈가 끝나는 지점이므로 54 를 지정한다. 따라서 Hex 값은 36 00 00 00 이 된다.

DIB Header

    • Offset 14~18 DIB Header 의 사이즈를 지정한다. BMP Header 에서 총 14 바이트를 사용했으니, 지금부터 40 바이트를 쓸 것이라고 지정한다.
    • Offset 18~22 BMP 이미지의 가로 사이즈를 지정한다. 샘플에선 2pixel 이 가로 사이즈 이므로, 02 00 00 00 이 지정된다.
    • Offset 22~26 BMP 이미지의 세로 사이즈를 지정한다. 샘플에선 2pixel 이 가로 사이즈 이므로, 02 00 00 00 이 지정된다.
    • Offset 26~28 BMP 이미지에서 사용할 Color Plane 번호를 지정한다 ( 1 : Red, 2 : Green, 3 : Blue => RGB )
    • Offset 28~30 픽셀당 비트 수를 지정한다. 24비트 BMP 샘플이므로 24를 지정한다.
    • Offset 30~34 BI_RGB 픽셀 압축 여부를 지정한다.
    • Offset 34~38 BMP Buffer 사이즈를 지정한다. Padding 값 까지 포함하여 16이다.
    • Offset 38~42 프린트 가로 해상도
    • Offset 42~46 프린트 세로 해상도
    • Offset 46~50 사용할 컬러 팔레트 지정
    • Offset 50~54 중요한 컬러 지정

Start of pixel array ( bitmap data )

    • Offset 54~57 RGB 값이 꺼꾸로 들어간다. 00 00 FF 값은 Blue 0, Green 0, Red 255 를 의미하므로  (0,1) 위치는 빨강색인 것이다.
    • Offset 57~60 동일하게 해석한다
    • Offset 60~62 4바이트 배열을 유지하기 위한 패딩 값
    • ~ 위 패턴 반복

위 내용대로 Bitmap Header 와 Buffer 값을 결합하는 코드를 C# 작성하면 아래와 같다. 개발자라면 코드를 보고 더 쉽게 이해할 것이다.

#region Bitmap Making... // BmpBufferSize : 헤더를 제외한 순수 버퍼 사이즈. // 54 값은 헤더 크기를 의미한다. byte[] BitmapBytes = new byte[BmpBufferSize + 54]; #region Bitmap Header // 0~2 "BM" BitmapBytes[0] = 0x42; BitmapBytes[1] = 0x4d; // 2~6 BMP 파일 사이즈를 의미한다. 헤더 값 54 + 버퍼 길이를 더해주면 된다. Array.Copy(BitConverter.GetBytes(BmpBufferSize + 54), 0, BitmapBytes, 2, 4); // 6~8 어플리케이션 세팅이므로 보통 0 이 들어간다. Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 6, 2); // 8~10 어플리케이션 세팅이므로 보통 0 이 들어간다. Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 8, 2); // 10~14 버퍼 데이터를 찾을 수 있는 Offset 을 지정한다. 24비트맵 이미지는 언제나 54 Offset 부터 버퍼 데이터가 저장된다. Array.Copy(BitConverter.GetBytes(54), 0, BitmapBytes, 10, 4); #endregion #region DIB Header // 14~18 DIB 헤더 사이즈를 지정한다. 40바이트 고정이다. Array.Copy(BitConverter.GetBytes(40), 0, BitmapBytes, 14, 4); // 18~22 비트맵의 가로 사이즈를 지정한다. Array.Copy(BitConverter.GetBytes(image.Width), 0, BitmapBytes, 18, 4); // 22~26 비트맵의 세로 사이즈를 지정한다. Array.Copy(BitConverter.GetBytes(image.Height), 0, BitmapBytes, 22, 4); // 26~28 컬러 플랜을 지정한다 1 : R 2 : G 3 : B Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 26, 2); // 28~30 픽셀당 비트 수를 지정한다. 24비트 비트맵이라면 24를 지정한다. 비디오나 이미지 해상도를 기반으로 지정하면 된다. if (image.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb) { Array.Copy(BitConverter.GetBytes(24), 0, BitmapBytes, 28, 2); } // 30~34 BI_RGB 값이며 버퍼를 압축할 것인지 여부를 지정한다. 실시간 처리에선 압축/압축해제시 CPU 소모가 크므로 보통 사용하지 않는다. Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 30, 4); // 34~38 버퍼의 순수 데이터 크기를 입력한다 ( 패딩 값 포함 ) Array.Copy(BitConverter.GetBytes(BmpBufferSize), 0, BitmapBytes, 34, 4); // 38~46 프린트 해상도를 지정한다. if (image.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb) { Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 38, 4); Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 42, 4); } // 46~50 팔레트에서 사용할 컬러 넘버를 지정한다. Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 46, 4); // 50~54 어떤 컬러가 중요한지 지정한다. 0 이면 모든 컬러가 중요하다는 의미이다. Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 50, 4); // 54~end : 픽셀 데이터이다. 드디어 힘들게 만든 헤더 데이터와 원본 버퍼 데이터를 결합하는 순간이다. Array.Copy(BmpBuffer as Array, 0, BitmapBytes, 54, BmpBufferSize); #endregion - bitmap header process #endregion - bitmap making process


이제 순수 Bitmap Buffer 데이터를 Header 정보를 결합해주었기 때문에 C# 에서 Bitmap 객체로 변환이 가능하다. Header 정보가 없으면 당연히 Bitmap 객체로 변환시 Buffer 데이터에 대한 명세서 ( Header ) 가 없기 때문에 컨버팅에서 에러가 발생한다.


 
























Add a bitmap header to an image buffer


In image processing, converting a raw buffer data from an IP Camera via TCP or RTSP and a web camera via USB interface to BMP, JPG, PNG and such a thing is a common, yet basic job. However if you don't know about the file structures, you might have some difficult time handling with the raw buffer data. This stackoverflow question illustrates that quite well.


I have a buffer from a camera, how can I convert it to BMP?

I've googled on that but only examples I could find was for C++ and in my code, converting results in ParameterException.

What should I do?


The key solution is to look at the BMP file structure. It's well addressed here in Wikipedia. You can see the table below in that page and it is a description of 2x2 24bit bitmap image here.





OffsetSizeHex ValueValueDescription
BMP Header
0h242 4D"BM"ID field (42h, 4Dh)
2h446 00 00 0070 bytes (54+16)Size of the BMP file
6h200 00UnusedApplication specific
8h200 00UnusedApplication specific
Ah436 00 00 0054 bytes (14+40)Offset where the pixel array (bitmap data) can be found
DIB Header
Eh428 00 00 0040 bytesNumber of bytes in the DIB header (from this point)
12h402 00 00 002 pixels (left to right order)Width of the bitmap in pixels
16h402 00 00 002 pixels (bottom to top order)Height of the bitmap in pixels. Positive for bottom to top pixel order.
1Ah201 001 planeNumber of color planes being used
1Ch218 0024 bitsNumber of bits per pixel
1Eh400 00 00 000BI_RGB, no pixel array compression used
22h410 00 00 0016 bytesSize of the raw bitmap data (including padding)
26h413 0B 00 002835 pixels/metre horizontalPrint resolution of the image,
72 DPI × 39.3701 inches per metre yields 2834.6472
2Ah413 0B 00 002835 pixels/metre vertical
2Eh400 00 00 000 colorsNumber of colors in the palette
32h400 00 00 000 important colors0 means all colors are important
Start of pixel array (bitmap data)
36h300 00 FF0 0 255Red, Pixel (0,1)
39h3FF FF FF255 255 255White, Pixel (1,1)
3Ch200 000 0Padding for 4 byte alignment (could be a value other than zero)
3Eh3FF 00 00255 0 0Blue, Pixel (0,0)
41h300 FF 000 255 0Green, Pixel (1,0)
44h200 000 0Padding for 4 byte alignment (could be a value other than zero)


BMP Header

    • Offset 0~1 put 0x42, 0x4D Hex values respectably.
    • Offset 2~6 put BMP file size here. The headers will sum up as 54, and the current sample data size is 16, so it's going to be added up to 70 bytes. Converting 70 decimal value to hex will be 46 00 00 00.
    • Offset 6~8 This is application specific, normally put 0 here
    • Offset 8~10 This is application specific, normally put 0 here
    • Offset 10~14 put the offset of starting bitmap buffer array. I don't want to state the obvious, but it's of course the end of the header, thus it's 54 and the hex value would be 36 00 00 00.

DIB Header

    • Offset 14~18 Set the DIB header size. It's 40 constant from here.
    • Offset 18~22 Set the horizontal size of the BMP image. This sample is 2x2, 2pixel is the horizontal size.
    • Offset 22~26 Set the vertical size of the BMP image. This sample is 2x2, 2pixel is the vertical size.
    • Offset 26~28 Set the number of color planes. ( 1 : Red, 2 : Green, 3 : Blue => RGB )
    • Offset 28~30 Set number of bits per pixel if it's a 24bit bitmap, set 24.
    • Offset 30~34 Set BI_RGB value to compress the buffer data or not.
    • Offset 34~38 Set the size of the buffer, including padding values. It's going to be 16.
    • Offset 38~42 Set print resolution of the horizontal lines
    • Offset 42~46 Set print resolution of the vertical lines
    • Offset 46~50 Set the colors in the palette.
    • Offset 50~54 Set what's important color in this image. 0 means everything is important.

Start of pixel array ( bitmap data )

    • Offset 54~57 Set RGB value backwards. 00 00 FF means the Blue 0, the Green 0, the Red 255, that's why (0,1) position is tinged with the red color.
    • Offset inspect it as right above.
    • Offset It's a padding for keeping 4 bytes array order.
    • ~ going over and over again.

This verbose rule can be written as a code in C# like this below. If you are a developer, this will give you much clearer idea of how to do this.

#region Bitmap Making... // BmpBufferSize : a pure length of raw bitmap data without the header. // the 54 value here is the length of bitmap header. byte[] BitmapBytes = new byte[BmpBufferSize + 54]; #region Bitmap Header // 0~2 "BM" BitmapBytes[0] = 0x42; BitmapBytes[1] = 0x4d; // 2~6 Size of the BMP file - Bit cound + Header 54 Array.Copy(BitConverter.GetBytes(BmpBufferSize + 54), 0, BitmapBytes, 2, 4); // 6~8 Application Specific : normally, set zero Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 6, 2); // 8~10 Application Specific : normally, set zero Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 8, 2); // 10~14 Offset where the pixel array can be found - 24bit bitmap data always starts at 54 offset. Array.Copy(BitConverter.GetBytes(54), 0, BitmapBytes, 10, 4); #endregion #region DIB Header // 14~18 Number of bytes in the DIB header. 40 bytes constant. Array.Copy(BitConverter.GetBytes(40), 0, BitmapBytes, 14, 4); // 18~22 Width of the bitmap. Array.Copy(BitConverter.GetBytes(image.Width), 0, BitmapBytes, 18, 4); // 22~26 Height of the bitmap. Array.Copy(BitConverter.GetBytes(image.Height), 0, BitmapBytes, 22, 4); // 26~28 Number of color planes being used Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 26, 2); // 28~30 Number of bits. If you don't know the pixel format, trying to calculate it with the quality of the video/image source. if (image.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb) { Array.Copy(BitConverter.GetBytes(24), 0, BitmapBytes, 28, 2); } // 30~34 BI_RGB no pixel array compression used : most of the time, just set zero if it is raw data. Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 30, 4); // 34~38 Size of the raw bitmap data ( including padding ) Array.Copy(BitConverter.GetBytes(BmpBufferSize), 0, BitmapBytes, 34, 4); // 38~46 Print resolution of the image, 72 DPI x 39.3701 inches per meter yields if (image.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb) { Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 38, 4); Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 42, 4); } // 46~50 Number of colors in the palette Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 46, 4); // 50~54 means all colors are important Array.Copy(BitConverter.GetBytes(0), 0, BitmapBytes, 50, 4); // 54~end : Pixel Data : Finally, time to combine your raw data, BmpBuffer in this code, with a bitmap header you've just created. Array.Copy(BmpBuffer as Array, 0, BitmapBytes, 54, BmpBufferSize); #endregion - bitmap header process #endregion - bitmap making process


As the bitmap header is well combined with the raw buffer data, you can now convert it to the Bitmap object in C#. You will always fail to change the raw data to Bitmap object unless you don't give the Bitmap object a proper bitmap header. It's a recipe for a bitmap image, you should always return it.


 



Comments