6.1. 배열을 사용하는 이유¶
Image 클래스는 그 위의 모든 메서드가 단일 고속 호출로 카메라의 네이티브 픽셀 버퍼에서 직접 작동하기 때문에 픽셀 작업에 적합한 도구입니다. 애플리케이션이 프레임에 대해 하는 일의 대부분 – 임계값 처리, 블롭 찾기, AprilTag 검출, 에지 필터 – 은 이미 그곳에 들어 있습니다.
이미지 라이브러리가 노출하지 않는 것은 OpenMV 애플리케이션이 마주치는 나머지 수치 작업입니다:
픽셀이 아닌 센서 버퍼 – ADC 샘플, IMU(관성 측정 장치)에서 나오는 축, 마이크 오디오,
어떤 내장 메서드도 반환하지 않는, 이미지에서 유도된 숫자 – 히스토그램 열, 두 프레임의 커스텀 혼합, 카탈로그가 다루지 않는 픽셀별 변환,
작은 선형대수 – 렌즈를 보정하는 교정 행렬, IMU를 융합하는 회전,
신호 처리 연산 – 진동 버퍼의 주파수 성분, 센서 출력에 적용되는 평활화, 분류기가 입력으로 원하는 특징 벡터.
이 모든 것은 같은 형태를 원합니다. 즉, 모든 요소에 하나의 연산을 적용하는 숫자 버퍼입니다. Python for 루프가 그것을 작성하는 명백한 방법입니다:
for i in range(len(samples)):
samples[i] = samples[i] * cal
루프는 동작합니다. 하지만 느리기도 합니다. Python은 인터프리터 언어이며, Python 루프의 모든 반복은 인터프리터를 한 번 실행하는 비용을 수반합니다. samples를 찾고, 요소 i를 읽고, 곱하고, 다시 쓰고, 루프 카운터를 증가시키고, 루프 조건을 확인하는 것입니다. 천 개의 센서 샘플로 이루어진 버퍼에서 이러한 인터프리터 비용은 근본적으로는 빠른 연산임에도 수십 밀리초로 합산됩니다.
그 오버헤드는 스크립트가 버퍼에 도달할 때마다 물어뜯습니다. QVGA 그레이스케일 프레임은 76,800픽셀입니다. 100Hz의 가속도계는 초당 100개의 3축 샘플을 전달합니다. 마이크는 64ms마다 1024 샘플 버퍼를 채웁니다. 이들 중 어느 것에 대한 순수 Python for 루프는 몇 마이크로초가 걸려야 할 작업을 수십 밀리초가 걸리는 작업으로 바꿔 놓으며 – 이미지 크기의 버퍼에서는 다시 대략 열 배 더 오래 걸립니다.
6.1.1. 라이브러리 함수가 루프보다 빠릅니다¶
해결책은 연산을 그 요소에 대한 Python 루프 대신 전체 버퍼에 대한 단일 함수 호출로 표현하는 것입니다. numpy가 바로 그것입니다. 모든 연산이 버퍼를 처음부터 끝까지 한 번 훑는 이미 최적화된 단일 함수인 배열 연산 라이브러리입니다. np.multiply(samples, cal)는 단일 호출 안에서 samples의 모든 요소에 cal을 곱합니다 – 반복당 인터프리터 비용 없이 루프가 했던 것과 같은 산술입니다. Python 루프로는 수십 밀리초가 걸렸던 동일한 1000개 요소 곱셈이 numpy 호출로는 수십 마이크로초가 걸립니다.
이것이 numpy가 전반적으로 제공하는 거래입니다. 합, 평균, sin, exp, 행렬 곱, 신호 처리 기본 연산 – 각각이 전체 버퍼를 한 번에 처리하는 단일 라이브러리 함수입니다. 그 대가는 데이터가 numpy의 배열 타입 안에 있어야 하고 연산이 요소를 하나씩 다루는 대신 그 배열에 대해 표현되어야 한다는 것입니다.
6.1.2. 리스트로는 안 되는 이유¶
Python list는 대신할 수 없습니다. 리스트는 어떤 객체의 조합이든 – 정수, float, 문자열, 다른 리스트 – 담을 수 있으며, 그것을 읽는 라이브러리 함수는 어떤 산술이 일어나기 전에 여전히 각 슬롯을 들여다보고 그 안에 무엇이 있는지 알아내고 값을 꺼내야 합니다. 그 슬롯별 오버헤드가 바로 Python 루프가 치르는 비용입니다. 리스트는 빠른 배열 연산에 맞지 않습니다.
6.1.3. bytearray도 충분하지 않은 이유¶
bytearray는 올바른 형태입니다 – 하나의 타입 지정 버퍼, 요소당 1바이트, 모두 하나의 연속된 블록 안에 있습니다. 대부분의 바이트 지향 주변장치 API가 돌려주는 것이 바로 이것입니다. 그것에 없는 것은 연산입니다. bytearray * 2는 각 값을 두 배로 만드는 대신 버퍼를 반복하며, bytearray + bytearray를 요소별로 더하는 것에는 합리적인 의미가 없습니다.
타입 지정 버퍼와 요소별 연산을 결합한 데이터 구조가 ndarray입니다. 상자 안에 무엇이 들어 있는지, 그리고 각 필드가 빠른 경로 동작을 어떻게 형성하는지가 이 장의 나머지가 기대는 기반입니다.