5.3. 픽셀 형식

에지를 검출하는 알고리즘은 각 픽셀이 밝기 값을 담고 있을 것으로 기대합니다. 색상이 있는 물체를 추적하는 알고리즘은 각 픽셀이 색상을 담고 있을 것으로 기대합니다. 모폴로지 클로징을 수행하는 알고리즘은 각 픽셀이 켜짐 또는 꺼짐 둘 중 하나일 것으로 기대합니다. Image 가 담고 있는 픽셀 형식 – 카탈로그에 열거된 Vision Sensors 중 하나 – 은 이러한 기대를 미리 검증할 수 있게 해줍니다. 즉, 형식은 픽셀이 어떤 형태로 되어 있는지, 따라서 변환 단계 없이 어떤 알고리즘을 실행할 수 있는지를 사전에 알려줍니다.

이 페이지는 그 제약이 실제로 어떻게 작용하는지를 다룹니다. 어떤 형식이 올바른 선택인지는 파이프라인이 무엇을 할 것인지에 따라 달라지며, 형식 간 변환 메서드는 둘 이상의 형식이 필요한 파이프라인이 단계들을 서로 연결하는 방법입니다.

레이블이 붙은 다섯 개의 바이트 레이아웃 스트립이 세로로 쌓여 있습니다. BINARY는 하나의 바이트가 여덟 개의 단일 비트 셀로 나뉜 모습을 보여주며 "바이트당 8픽셀"로 표시됩니다. GRAYSCALE은 각각 "1픽셀"로 표시된 세 개의 단일 바이트 셀을 보여줍니다. RGB565는 RRRRR GGGGGG BBBBB 비트 필드를 가진 인접한 두 바이트를 보여주며 "1픽셀"로 표시됩니다. YUV422는 레이블이 붙은 네 개의 바이트 셀 Y0, U, Y1, V를 보여주며 "2픽셀"로 표시됩니다. BAYER는 레이블이 붙은 네 개의 바이트 셀로 이루어진 두 행을 보여줍니다. 위쪽 행은 R G R G, 아래쪽 행은 G B G B입니다.

다섯 가지 비압축 픽셀 형식과 그 바이트들이 어떻게 패킹되는지를 나타냅니다. JPEG와 PNG는 고정 크기의 픽셀 격자가 아니라 가변 길이의 압축 스트림이기 때문에 여기에 그려지지 않았습니다.

5.3.1. 그레이스케일이라는 일꾼

고전적인 머신 비전의 대부분은 결국 밝기 값을 다루는 일로 귀결됩니다. 에지 검출, 템플릿 매칭, AprilTag 디코딩, 옵티컬 플로우 추정, 모폴로지 연산자, 블롭 분석 – 이 모두는 알고리즘이 동작하는 수준에서 보면 각 픽셀이 얼마나 밝은지, 그리고 그 밝기가 인접 픽셀의 밝기와 어떻게 비교되는지를 살펴봅니다. 장면의 색상은 이러한 알고리즘을 호출하는 애플리케이션에는 종종 유용하지만, 알고리즘 자체에는 필요하지 않습니다.

그레이스케일 형식은 어떠한 오버헤드도 없이 알고리즘에 정확히 그것을 제공합니다. 픽셀당 1바이트가 0 (검정)부터 255 (흰색)까지의 밝기 값을 담습니다. 이 형식은 RGB565와 YUV422의 절반 크기이고 RGB888의 1/3 크기여서, 모든 연산이 더 적은 데이터를 처리하게 되어 – 더 빠르고 버퍼 부담도 더 적습니다. 프레임 버퍼가 스크립트의 나머지 부분과 RAM을 두고 경쟁하는 더 작은 카메라에서는, 이 메모리 점유량 차이가 파이프라인이 아예 들어맞는지 여부를 결정할 수 있습니다. 색상이 알고리즘에 필요한 단서가 아니라면, 그레이스케일이 올바른 답입니다.

5.3.2. RGB565를 통한 색상

색상이 바로 그 단서일 때 – 색상 마커를 추적하거나, 빨간 사과를 초록 사과와 구별하거나, 색조로 UI 요소를 골라낼 때 – 픽셀당 2바이트는 알고리즘이 수행하는 종류의 분류에 충분한 색상을 확보해 줍니다. RGB565는 카메라의 기본 색상 형식이며, 표면의 색상 인식 메서드가 기대하는 형식입니다.

주석이 달린 프레임을 렌더링하는 것 – 검출 상자를 그리거나, 진단 텍스트를 쓰거나, 프레임을 화면에 띄우거나 원격 뷰어로 내보내는 것 – 역시 자연스럽게 RGB565를 필요로 합니다. IDE 미리보기, 온보드 디스플레이 컨트롤러, 그리고 대부분의 네트워크 대상은 이 형식을 직접 소비하거나 저렴하게 변환합니다.

5.3.3. 저장 형식으로서의 Bayer

Bayer 이미지는 ISP가 완성된 색상 표현으로 디베이어링하기 전의 원시 센서 출력입니다. 각 픽셀은 하나의 색상 채널을 담는 1바이트입니다 – 모자이크의 해당 위치에 있는 컬러 필터가 통과시킨 채널입니다. 따라서 Bayer 이미지는 그레이스케일 이미지와 같은 크기이고 RGB888의 1/3 크기인데, 이는 Bayer가 실제로 유용한 용도와 부합합니다. 즉, RAM이 제약 조건일 때 한 번에 여러 프레임을 저장하는 것입니다.

문제는 image 모듈의 알고리즘이 Bayer 이미지를 직접 처리하지 않는다는 점입니다. 디베이어링 없이는 어떤 픽셀도 단독으로 색상을 판단할 만큼 충분한 정보를 담지 못하며, 알고리즘이 찾는 패턴 – 에지, 코너, 블롭 – 은 모자이크에 의해 왜곡될 것입니다. Bayer 이미지를 읽거나 수정하는 유일한 방법은 get_pixel()set_pixel() 뿐이며, 그 외 모든 것은 완성된 표현을 기대합니다.

여기서 도출되는 패턴은, 프레임이 큐에 머물러 있어야 하는 동안에는 Bayer로 저장해 두었다가 실제 처리가 시작되는 순간에 각 프레임을 그레이스케일 또는 RGB565로 변환하는 것입니다. 변환은 CPU 사이클을 소모하지만, 그렇지 않았으면 애플리케이션이 동작하는 내내 완성된 프레임을 붙들고 있느라 묶여 있었을 RAM을 절약해 줍니다.

참고

image 모듈이 Bayer 픽셀에 직접 수행하는 유일한 연산은 get_pixel(), set_pixel(), 그리고 IDE 미리보기나 원격 뷰어로 공급되는 JPEG 인코딩 경로입니다. 그리기, 분석, 필터링은 모두 먼저 그레이스케일, RGB565, 또는 바이너리로 변환해야 합니다.

5.3.4. 둘 다 원하는 파이프라인을 위한 YUV422

YUV422는 각 픽셀의 정보를 휘도 채널(Y)과 두 개의 색차 채널(U와 V)로 분리하고, 색차를 서브샘플링하여 인접한 픽셀 쌍이 하나의 U와 하나의 V를 공유하도록 합니다. 픽셀당 바이트 수는 평균적으로 2가 됩니다 – RGB565와 동일합니다 – 하지만 그것들은 Y 채널이 버퍼 내 알려진 오프셋에 위치한 연속적인 8비트 그레이스케일 이미지가 되도록 배치되어 있습니다.

그 배치는 일부 단계는 그레이스케일 작업이고 일부는 색상이 필요한 파이프라인이 원하는 바로 그것입니다. 그레이스케일 단계에서 Y 값을 직접 읽으면 명시적인 변환 비용을 건너뛸 수 있고, 이후 단계에서 실제로 색상이 필요할 때 U와 V 채널이 거기에 있습니다. 그 특정 패턴을 벗어나면, 색상에는 보통 RGB565가 더 단순한 선택이고 밝기만 다루는 작업에는 그레이스케일이 더 단순한 선택입니다 – YUV422의 가치는 두 가지를 동시에 잘 해낸다는 점에서 나옵니다.

참고

image 모듈은 그레이스케일, RGB565, 또는 바이너리보다 더 제한적인 방식으로 YUV422를 처리합니다 – 그레이스케일 작업을 위한 직접적인 Y 채널 읽기와 IDE 미리보기나 원격 뷰어로 공급되는 JPEG 인코딩 경로입니다. 색상 인식 메서드는 RGB565를 기대하므로, YUV422 프레임은 색상 분석이나 그리기에 앞서 명시적인 변환이 필요합니다.

5.3.5. 바이너리, 마스크, 그리고 임계값 처리된 출력

바이너리 이미지는 픽셀당 1비트입니다. 각 픽셀은 0 또는 1 입니다. 이 형식은 센서 캡처로는 거의 나타나지 않습니다. 대신, 임계값 처리(색상 또는 밝기 테스트가 각 픽셀을 “예, 일치함” 또는 “아니오, 일치하지 않음”으로 분류하는 경우)의 자연스러운 출력으로, 그리고 모폴로지 연산과 많은 메서드가 받아들이는 mask 인자의 자연스러운 입력으로 등장합니다.

이 형식의 실용적인 장점은 그 크기입니다. 바이너리 이미지는 그레이스케일 이미지가 차지하는 공간의 8분의 1이므로, 큰 마스크 – 어떤 다운스트림 연산이 다뤄야 할 위치를 픽셀별로 선택한 것 – 를 가지고 다니는 비용이 저렴합니다. 많은 연산이 바이너리 이미지를 mask= 키워드 인자로 받아들인다는 사실은 같은 요점의 다른 측면입니다. 즉, 이 형식은 작고, 한 단계의 바이너리 출력을 다른 단계의 마스크 입력으로 연결하는 것은 흔한 파이프라인 패턴입니다.

5.3.6. 경계에서의 JPEG와 PNG

JPEG와 PNG Image 객체는 카탈로그의 다른 것들과 다릅니다. 그것들은 픽셀 격자가 아닙니다. 픽셀 데이터를 픽셀 수준 연산이 읽을 수 없는 형태로 인코딩한 압축된 바이트 스트림입니다. JPEG에 대해 get_pixel() 을 호출해도 해당 위치의 픽셀을 반환하지 않습니다. 메서드가 가져올 수 있도록 버퍼 어딘가에 풀려 있는 픽셀이 존재하지 않기 때문입니다.

JPEG와 PNG는 이미지 처리의 경계에서, 즉 픽셀 데이터가 압축된 형태로 카메라를 떠나거나 들어오는 지점에서 등장합니다. 프레임을 JPEG로 디스크에 저장하면 파일이 작게 유지되고, 프레임을 JPEG로 네트워크를 통해 보내면 전송 비용이 저렴하게 유지되며, JPEG 파일에서 기준 프레임을 불러오면 원시 픽셀보다 훨씬 작은 형태로 디스크에 머물 수 있습니다. 이러한 용도 중 어느 것이든 압축된 표현이 올바른 답입니다. 하지만 JPEG에 실제 처리를 수행하려면, 애플리케이션은 먼저 그것을 작업 가능한 형식으로 변환합니다 – 그리고 그 변환이 바로 압축된 바이트가 픽셀로 확장되고 버퍼가 부풀어 오르는(30 KB JPEG가 600 KB의 RGB565가 될 수 있습니다) 지점입니다.

5.3.7. 형식 간 변환

변환 경로는 서로 다른 형식을 하나의 파이프라인으로 엮어주는 것입니다. Image 클래스의 다섯 가지 메서드는 기존 이미지를 받아 다른 형식의 새 이미지를 반환합니다:

  • to_grayscale() 은 고전적인 알고리즘이 원하는 형식인, 픽셀당 1바이트 이미지를 생성합니다.

  • to_rgb565() 는 색상 인식 메서드와 IDE 미리보기가 모두 사용하는 픽셀당 2바이트 색상 형식을 생성합니다.

  • to_bitmap() 은 모폴로지와 mask 인자가 받아들이는 형식인, 1비트 바이너리 이미지를 생성합니다.

  • to_jpeg() 는 저장이나 전송에 적합한 JPEG 압축 이미지를 생성합니다.

  • to_png() 은 JPEG의 더 작은 파일보다 무손실 인코딩이 선호될 때 PNG 압축 이미지를 생성합니다.

각 변환은 기본적으로 제자리에서 실행됩니다. 즉, 원본 이미지의 버퍼가 변환된 결과로 덮어쓰이고, 호출이 반환된 후에는 원본의 원래 픽셀이 사라집니다. 이는 CPU와 메모리 양쪽 모두에서 가장 저렴한 선택이며, 원본 프레임이 다른 어떤 용도로도 필요하지 않을 때 올바른 답입니다.

원본이 여전히 필요할 때 – 파이프라인의 이후 단계가 원본 프레임을 봐야 할 때 – 두 개의 키워드 인자가 제자리 변환 기본값을 재정의합니다. copy=True 는 변환된 이미지를 위해 Python 힙에 별도의 버퍼를 할당하고 원본을 그대로 둡니다. copy_to_fb=True 는 동일한 할당을 수행하되 힙 대신 프레임 버퍼에 넣습니다 – 변환된 이미지가 IDE 미리보기에 들어가야 할 때 애플리케이션이 사용하는 방식인데, IDE가 프레임 버퍼에서 읽기 때문입니다.

또 다른 두 메서드는 직접 변환이 아니라 팔레트 를 통해 색상을 입힌 RGB565 이미지를 생성합니다. to_rainbow() 는 각 단일 채널 입력 값을 가시광선 스펙트럼을 관통하는 부드러운 그라데이션을 따라 색상에 매핑합니다. to_ironbow() 는 각 입력 값을 검정에서 어두운 빨강과 주황을 거쳐 흰색까지 이어지는 비선형 열화상 팔레트에 매핑합니다. 둘 다 측정 도구가 아니라 시각화 도구입니다. 요점은 원시 값이 그대로라면 눈에 보이지 않았을 단일 채널 이미지를 한눈에 읽을 수 있게 만드는 것입니다.

5.3.8. 버퍼 크기

형식에 관해 명시할 가치가 있는 마지막 세부 사항 하나입니다. size() 는 항상 픽셀 수가 아니라 바이트 버퍼 크기를 보고합니다. 비압축 형식의 경우 이는 치수와 픽셀당 바이트로부터 곧바로 도출됩니다. 즉, width * height * bytes_per_pixel 입니다. JPEG와 PNG의 경우 이는 압축된 스트림의 크기이며, 장면에 담긴 내용에 따라 프레임마다 달라집니다. 바이트 예산에서 버퍼를 할당하는 코드는 전자의 경우에 size() 를 사용하고, 카메라에서 압축된 프레임을 스트리밍하는 코드는 스트림이 실제로 몇 바이트를 담고 있는지 알기 위해 각 압축 후에 그것을 읽습니다.