5.9. 산술 연산

이전 절의 그리기 계열은 이미지 안에 그립니다. 산술 계열은 두 이미지를 결합하여 세 번째 이미지를 만듭니다. 즉 두 이미지의 픽셀 값을 더하거나, 하나에서 다른 하나를 빼거나, 모든 위치에서 최솟값 또는 최댓값을 취합니다. 이 작은 픽셀 단위 산술 연산 집합은 프레임 차분, 배경 차감, 노출 스태킹을 비롯한 여러 고전적인 패턴이 그 위에 구축되는 기반입니다.

Image 클래스의 산술 계열은 한꺼번에 나열할 수 있을 만큼 작습니다:

  • add() – 픽셀 단위 self + other, 형식의 최댓값으로 잘림.

  • sub() – 픽셀 단위 self - other, 하단에서 0 으로 잘림.

  • rsub() – 픽셀 단위 other - self, 0 으로 잘림(피연산자를 뒤바꾼 sub 와 동일한 산술).

  • min() – 두 값의 픽셀 단위 최솟값.

  • max() – 픽셀 단위 최댓값.

  • difference() – 픽셀 단위 |self - other|, 절댓값 차이.

여기에 관련된 단일 이미지 연산 두 가지가 더 있습니다:

  • invert() – 각 픽셀을 255 - pixel (또는 형식에 상응하는 최댓값)로 치환.

  • negate()invert() 의 별칭.

상단에 소스 이미지 A와 B를 나타내는 수평 그래디언트 막대 두 개가 있습니다. A는 왼쪽에서 오른쪽으로 어두운 색에서 밝은 색으로, B는 왼쪽에서 오른쪽으로 밝은 색에서 어두운 색으로 변합니다. 그 아래에는 A와 B에 적용된 각 쌍별 연산의 결과를 나타내는 다섯 개의 그래디언트 막대가 있습니다. A.add(B)는 모든 위치에서 합이 255를 넘어 잘리기 때문에 균일한 흰색으로 나타납니다. A.sub(B)는 왼쪽 절반이 0이고 오른쪽으로 갈수록 밝아집니다. A.difference(B)는 V 모양을 보이며, 양쪽 끝이 밝고 가운데가 어둡습니다. A.min(B)는 양쪽 끝이 어둡고 가운데가 더 밝습니다. A.max(B)는 양쪽 끝이 밝고 가운데가 회색입니다.

두 소스 그래디언트 A와 B, 그리고 그것들에 적용된 각 쌍별 연산의 결과. 모든 연산은 위치별로 실행됩니다. 즉 어느 한 위치에서 결과에 나타나는 값은 그 위치의 두 소스 픽셀에만 의존합니다.

5.9.1. 두 가지 피연산자 형식

두 이미지를 다루는 각 메서드는 두 번째 피연산자로 다음 두 형식 중 하나를 받아들입니다:

  • 같은 크기의 또 다른 Image. 산술은 위치별로 실행됩니다. (x, y) 에서의 결과는 두 이미지 모두의 (x, y) 에 있는 소스 픽셀에 적용된 연산입니다.

  • 스칼라 값 – 그레이스케일의 경우 정수, RGB565의 경우 (r, g, b) 튜플. 같은 스칼라가 모든 위치에 적용됩니다.

스칼라 형식은 애플리케이션이 모든 픽셀을 일정한 양만큼 이동시키고자 할 때 유용합니다. img.add(40) 은 이미지 전체를 40만큼 밝게 합니다. img.sub((20, 20, 20)) 은 채널당 모든 픽셀을 20만큼 어둡게 합니다. img.max(50) 은 50 미만인 픽셀을 모두 50으로 끌어올리고 나머지는 그대로 둡니다. 이는 거의 검은색에 가까운 센서 바닥값을 후속 단계가 처리할 수 있도록 균일한 어두운 회색으로 바꾸는 종류의 연산입니다.

5.9.2. 잘림(Clipping)

픽셀 값은 모든 연산을 통틀어 형식의 범위 안에 유지됩니다. 8비트 채널의 경우 이는 0255 를 의미합니다. 255 를 넘어 오버플로될 값은 모두 255 로 잘려 되돌려지고, 0 아래로 내려갈 값은 모두 0 으로 끌어올려집니다. 래핑(wrap-around)은 없습니다.

이 선택은 실제로 중요합니다. add 로 픽셀을 밝게 할 때, 산술이 그렇지 않았다면 오버플로될 밝은 쪽 끝에서 갑작스러운 어두워짐 아티팩트가 결코 발생하지 않습니다. sub 로 픽셀을 어둡게 할 때, 그렇지 않았다면 언더플로될 어두운 쪽 끝에서 갑작스러운 밝아짐 아티팩트가 결코 발생하지 않습니다. 결과는 포화된 극단에서 약간의 정보 손실을 감수하는 대신 시각적으로 의미 있는 상태를 유지합니다.

잘림은 또한 subrsub 가 서로 다른 결과를 반환하는 이유이기도 합니다. img_a.sub(img_b)ab 보다 밝은 부분을 주고 그 외 모든 곳은 0입니다. img_a.rsub(img_b)ba 보다 밝은 부분을 줍니다. 둘 중 하나는 단방향 변화 검출에 유용합니다. 애플리케이션이 밝아진 픽셀에만 관심이 있거나 어두워진 픽셀에만 관심이 있는 경우입니다. 하지만 어느 쪽도 두 프레임 사이의 모든 변화를 포착하지는 않습니다.

5.9.3. 차이 연산

양방향 변화 검출을 위해 사용할 연산은 difference() 이며, 이는 모든 위치에서 |self - other| 를 계산합니다. 즉 부호 없는 절댓값 차이입니다. 어느 방향으로든 변화한 모든 픽셀이 결과에서 0이 아닌 값으로 나타나며, 그 크기는 해당 위치에서 얼마나 변화했는지에 비례합니다.

두 이미지가 일치하지 않는 곳에서 정확히 0이 아닌 값이 된다는 이 속성이 difference 를 프레임 단위 변화 검출의 주력 도구로 만드는 이유입니다. 시작 시점에 저장한 참조 프레임과 새로 캡처한 프레임을 difference 로 처리하면, 0이 아닌 픽셀이 장면에서 무언가 움직이거나 밝기가 변한 모든 위치를 표시하는 이미지가 만들어집니다.

5.9.4. 마스크로 범위 지정

모든 산술 메서드는 영역 및 마스크 페이지에서 소개한 mask 키워드 인수를 받아들입니다. 마스크가 전달되면 연산은 마스크가 0이 아닌 위치에서만 실행되고, 그 외의 모든 곳에서는 대상 이미지가 그대로 유지됩니다.

이러한 조합은 두 가지 패턴으로 나타납니다. 첫 번째는 연산을 알려진 영역으로 제한하는 것입니다. 예를 들어 검출된 마커의 경계 상자 안에서만 두 프레임을 더하는 것입니다. 두 번째는 복합 프레임을 조각조각 쌓아 올리는 것입니다. 전경 마스크 안에서 일련의 프레임에 대해 min 을, 그 보완 마스크 안에서 같은 시퀀스에 대해 max 를 적용하는 식의 패턴입니다.

5.9.5. 제자리 연산과 입력 보존

산술 메서드는 모두 앞서 확립된 동작 규약을 따릅니다. 각 메서드는 소스 이미지를 제자리에서 수정하고 체이닝을 위해 같은 이미지를 반환합니다. 소스의 픽셀은 호출 후 사라지며, 두 번째 피연산자로 전달된 것에 대한 연산 결과로 대체됩니다.

애플리케이션이 두 입력을 모두 보존해야 할 때, 안전한 패턴은 그중 하나를 먼저 복사하는 것입니다:

diff = current.copy()       # leaves current intact
diff.difference(reference)  # diff now holds the absolute difference

복사한 다음 연산하는 이 패턴은 모든 프레임 차분 파이프라인의 근간이며, 여기서는 참조 프레임이 비교에서 살아남아야 다음 캡처 프레임에서 재사용할 수 있습니다.

여섯 가지 결합 연산, 두 가지 단일 이미지 연산, 절댓값 차이라는 주력 도구, 그리고 범위 지정을 위한 mask 키워드를 갖춘 픽셀 산술 도구 모음은 고전적인 머신 비전이 필요로 하는 밝기 및 채널 조합을 포괄합니다. 표면적으로 산술과 비슷한 나머지 도구는 값 단위가 아니라 비트 단위로 동작합니다.