4.14. CSI 기초

csi 모듈은 Python 코드가 카메라 센서를 구동하는 방법입니다. 프레임을 캡처하는 모든 스크립트는 동일한 세 부분 형태를 따릅니다: 맨 위의 import, 중간의 일회성 설정, 그리고 맨 아래에서 카메라로부터 프레임을 한 번에 하나씩 가져오는 while True 루프입니다.

4.14.1. 일반적인 루프

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. 각 호출이 하는 일

import csi, image, time

세 모듈을 가져옵니다. csi 는 센서를 제어하고, imagesnapshot() 이 반환하는 Image 클래스를 정의하며, time 은 초당 프레임 수를 측정하는 데 사용되는 time.clock() 헬퍼를 제공합니다.

csi.CSI()

하나의 물리적 카메라 센서를 감싸는 CSI 인스턴스를 생성합니다. 생성자는 카메라 주변장치를 점유하고 센서별 설정을 기록합니다. 단일 센서를 가진 카메라는 하나의 CSI 인스턴스를 가지며, 두 개의 센서(색상과 열화상, 색상과 이벤트)를 가진 카메라는 두 개를 가지는데 각각 생성자의 cid 인자로 선택됩니다.

csi0.reset()

센서에 전원을 공급하고 구성합니다. 기본적으로 센서의 리셋 핀을 펄스한 다음, 센서의 I2C 레지스터를 알려진 초기 상태로 씁니다. 이후의 구성 호출 – pixformat, framesize, 자동 제어 노브 – 은 동일한 I2C 제어 버스를 통해 더 많은 레지스터 쓰기를 전달합니다.

csi0.pixformat(csi.RGB565)

출력 픽셀 포맷을 선택하는 센서 레지스터를 씁니다. 사용 가능한 선택지는 픽셀 포맷 페이지에서 소개한 포맷들입니다: RGB565, GRAYSCALE, BAYER, YUV422, 그리고 이를 지원하는 센서에서는 JPEG.

csi0.framesize(csi.QVGA)

출력 해상도를 선택하는 레지스터를 씁니다. QVGA 는 320 × 240이며, 명명된 크기는 이를 지원하는 센서에서 WQXGA2 (2592 × 1944, 약 5 MP)까지 올라갑니다. 사용자 정의 (width, height) 튜플도 센서의 출력 능력에 부합하는 한 사용할 수 있습니다.

clock = time.clock()

클럭 헬퍼를 생성합니다. 루프 안에서 clock.tick() 을 호출할 때마다 반복 시작 시간을 기록하며, time.clock.fps() 는 최근 루프 속도를 초당 프레임 수로 보고합니다.

img = csi0.snapshot()

센서에서 한 프레임을 캡처하여 Image 로 반환합니다. 그 프레임이 메모리에 들어가는 방식의 메커니즘은 자세히 살펴볼 만합니다.

4.14.3. 스냅샷이 메모리를 채우는 방법

센서는 센서 버스 에서 설명한 픽셀 데이터 버스로 초당 수백 메가바이트의 속도로 픽셀을 전달합니다 – CPU가 소프트웨어로 픽셀 단위로 복사하기에는 너무 빠릅니다.

대신 MCU는 전송을 직접 메모리 접근(Direct Memory Access, DMA) 에 위임합니다 – DMA는 CPU와 별개의 하드웨어 엔진으로, CPU를 전혀 관여시키지 않고 MCU 내부에서 한 곳에서 다른 곳으로 바이트를 복사합니다. 카메라 입력 주변장치는 들어오는 각 픽셀 바이트를 작은 온칩 FIFO에 받아들이고, MCU 측에서 실행되는 ISP 단계들이 통과하는 데이터를 처리하며, DMA 엔진이 완성된 픽셀을 해당 픽셀 오프셋으로 RAM 내 프레임 버퍼에 씁니다. 일단 DMA 채널이 프로그래밍되면 이 사슬에서 어느 것도 CPU를 필요로 하지 않습니다.

snapshot() 이 호출되면:

  1. CSI 드라이버는 프레임 버퍼의 주소, 전송 길이(한 프레임 분량의 픽셀), 그리고 DMA 완료 인터럽트를 위한 콜백으로 DMA 엔진을 프로그래밍합니다.

  2. 드라이버는 카메라 입력 주변장치를 활성화하고 센서가 다음 프레임의 시작을 알릴 때까지 기다립니다.

  3. 센서가 프레임을 스트리밍해 내보내면, 주변장치는 각 픽셀 바이트를 ISP를 거쳐 DMA 엔진으로 넘기고, DMA 엔진은 그 결과를 다음 프레임 버퍼 오프셋으로 RAM에 씁니다. CPU는 전송 중에 다른 코드를 자유롭게 실행할 수 있습니다.

  4. 프레임의 마지막 픽셀이 도착하면, DMA가 완료 인터럽트를 발생시키고, 드라이버는 프레임 버퍼를 Image 로 감싸며, snapshot() 이 이를 사용자 코드에 반환합니다.

반환된 Image 은 픽셀 데이터의 복사본을 소유하지 않습니다 – 이는 RAM 내 카메라 프레임 버퍼 중 하나를 가리킵니다. 카메라가 얼마나 많은 프레임 버퍼를 유지하는지, 그리고 snapshot() 호출마다 그것들이 DMA와 사용자 코드 사이에서 어떻게 넘겨지는지는 애플리케이션이 framebuffers() 를 통해 선택한 버퍼링 모드에 따라 달라집니다.