5.16. 사용자 정의 컨볼루션 커널¶
지금까지 다룬 이웃 필터들은 각각 윈도우의 모든 위치에 적용하는 내장 통계량을 가지고 있었습니다 – 평균, 가우시안 가중 평균, 중앙값입니다. morph() 는 애플리케이션이 직접 통계량을 커널 형태로 제공할 수 있게 해 주는 유일한 필터입니다. 커널이란 필터가 이웃 픽셀들을 하나의 출력 값으로 결합하는 방법을 기술하는 작은 가중치 행렬입니다.
그 메커니즘은 고전적인 컨볼루션 연산입니다. 각 출력 위치에서 모든 이웃 픽셀이 커널의 대응되는 가중치와 곱해지고, 그 곱들이 합산되며, 결과가 선택적으로 스케일과 오프셋이 적용된 뒤 출력 픽셀에 기록됩니다. 같은 입력이라도 커널이 다르면 다른 결과가 나옵니다. 모두 동일한 양의 가중치를 가진 커널은 mean() 필터를 재현하고, 종 모양의 커널은 gaussian() 을 재현합니다. 그 외의 패턴들은 에지 응답, 엠보스, 그래디언트, 샤프닝, 모션 블러, 그리고 그 밖의 길고 긴 효과 목록을 만들어 냅니다 – 고전적인 이미지 처리가 단일 선형 패스로 하고 싶어 했던 모든 것입니다.
5.16.1. morph 메서드¶
시그니처는 다른 이웃 필터들과 비슷하되 인자가 하나 더 추가되어 있습니다:
img.morph(size, kernel, mul=1.0, add=0.0)
size 는 다른 곳에서와 동일하게 반경을 의미하므로, 커널은 정확히 (2 * size + 1) 행 곱하기 (2 * size + 1) 열이어야 합니다. 커널 자체는 그만큼의 숫자들로 이루어진 평탄한 Python 리스트이며, 행 우선(row-major) 순서로 배열됩니다 – 처음 (2 * size + 1) 개 항목이 맨 윗 행이고, 다음 (2 * size + 1) 개가 두 번째 행이며, 이런 식으로 맨 아랫 행까지 이어집니다. mul 은 곱의 합이 출력 픽셀에 기록되기 전에 그 값을 스케일하고, add 는 상수를 더합니다. 기본값인 mul=1.0 과 add=0.0 은 컨볼루션 출력을 변경하지 않습니다.
명확히 짚고 넘어갈 만한 세부 사항이 하나 있습니다: 이 메서드는 출력을 기록하기 전에 곱의 합을 커널 항목들의 합 으로 자동으로 나눕니다. 이 자동 나눗셈 덕분에 항목들의 합이 9인 평균화 커널 – 예를 들어 3 곱하기 3 박스 블러 – 은 별도의 노력 없이 1/9 스케일로 나오고, 합이 16인 가우시안 근사 커널은 1/16 스케일로 나오며, 두 경우 모두 애플리케이션이 직접 나눗셈을 계산할 필요가 없습니다. 애플리케이션은 자동 정규화 위에 추가적인 스케일을 적용하고 싶을 때 – 또는 더 흔하게는 커널의 합이 0이어서(에지 응답 커널) 자동 나눗셈이 아무것도 아닌 값으로 나누는 셈이 될 때 – 에만 mul 을 설정합니다. 그런 경우 프레임워크는 합을 1로 취급하며, mul 은 스케일되지 않은 곱의 합을 범위 안에 유지하는 유일한 조절 수단이 됩니다.
적응형 임계값 섹션의 threshold=True / offset=N 쌍은 morph() 에서도 동작하므로, 동일한 사용자 정의 커널 프레임워크로 컷오프가 사용자 정의 통계량으로 계산되는 이진 임계값을 만들어 낼 수 있습니다.
5.16.2. 커널 레이아웃¶
3 곱하기 3 커널(size=1)은 왼쪽에서 오른쪽으로, 위에서 아래로 배열된 9개 숫자의 평탄한 리스트입니다. 리스트를 Python의 세 줄에 걸쳐 나누어 쓰면 그 관례가 자연스럽게 읽힙니다:
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
이것은 Sobel-x 그래디언트 연산자입니다 – 어떤 애플리케이션이든 가장 먼저 원하게 될 표준 커널이자 처음부터 끝까지 따라가 보기에 유용한 커널입니다. 패턴은 단순합니다: 왼쪽 열에 음의 가중치, 오른쪽 열에 양의 가중치를 두고, 가운데 열은 0입니다. 행 가중치 -1, -2, -1 (오른쪽에서는 1, 2, 1)은 모서리보다 가운데에서 더 크며, 이는 가운데 행이 모서리 행보다 결과에 더 큰 영향을 주게 합니다.
커널이 수직 에지 – 왼쪽은 어둡고 오른쪽은 밝게 변하는 픽셀 열 – 를 가로질러 훑을 때, 음의 가중치는 어두운 쪽을, 양의 가중치는 밝은 쪽을 받아들입니다. 곱의 합은 큰 양수가 되고, 필터는 이를 밝은 출력 픽셀로 기록합니다. 균일한 밝기의 수평 영역은 0을 만들어 내는데, 이는 모든 양의 가중치가 동일한 값을 가진 픽셀에서 같은 크기의 음의 가중치와 짝을 이루기 때문입니다.
커널 실행하기:
img.morph(1, sobel_x, mul=0.25)
Sobel 커널의 합은 0입니다 – 왼쪽의 모든 음의 가중치가 오른쪽의 같은 크기 양의 가중치와 짝을 이루기 때문입니다 – 따라서 자동 나눗셈은 아무것도 나누지 않으며, mul 이 곱의 합에 대한 유일한 스케일이 됩니다. mul=0.25 는 응답을 범위 안에 유지합니다: Sobel-x가 3 곱하기 3 영역에서 만들어 낼 수 있는 최대 절대 합은 대략 4 * 255 = 1020 (2 까지 가중된 8개의 밝은 픽셀)이며, 이를 4로 나누면 극단적인 경우가 255 에 닿아 포맷이 이를 깔끔하게 클립합니다.
이에 대응하는 Sobel-y 커널은 동일한 가중치 패턴을 90도 회전하여 수평 에지를 검출합니다:
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
방향에 상관없이 모든 에지를 검출하고 싶은 애플리케이션은 일반적으로 두 Sobel을 모두 실행한 뒤 그 응답들을 결합합니다.
5.16.3. 출력 오프셋 적용하기¶
add 는 스케일링 이야기의 나머지 절반입니다. 합이 0인 커널의 응답은 부호가 있는 값으로 – 에지의 한쪽에서는 양수, 다른 쪽에서는 음수입니다 – 음수 절반은 부호 없는 픽셀에 기록될 때 0으로 클립됩니다. add=128 은 응답을 중간 회색에 중심을 두도록 이동시켜, 음의 응답은 128 미만의 값으로 살아남고 양의 응답은 그 위에 놓입니다: 에지 응답이나 엠보스가 양쪽 방향 모두에서 보이게 되며, 그 대가로 각 방향의 범위는 절반이 됩니다.
커널이 어떤 mul 과 add 조합을 기대하는지는 커널 설계의 일부입니다. 표준 커널 카탈로그 에 각 일반적인 커널에 맞는 올바른 설정이 나열되어 있습니다.
5.16.4. 더 큰 커널¶
이 페이지의 모든 내용은 3 곱하기 3 커널(size=1)로 설명되었는데, 이는 표준 카탈로그가 사용하는 크기이고 그 크기에서는 행 우선 레이아웃을 손으로 적어 내기가 쉽기 때문입니다. 하지만 그 메커니즘에서 커널을 3 곱하기 3으로 제한하는 것은 없습니다. size=2 는 평탄한 리스트에 25개 항목을 가진 5 곱하기 5 커널을 실행하고, size=3 은 49개를 가진 7 곱하기 7을 실행하며, 이런 식으로 애플리케이션이 비용을 감당할 의향이 있는 어떤 반경까지든 가능합니다. 프레임워크는 어떤 홀수 크기에서든 평탄한 리스트 또는 중첩된 행 레이아웃을 모두 처리합니다.
더 큰 커널을 사용하는 이유는 어떤 내장 필터에서든 더 큰 이웃을 사용하는 이유와 동일합니다: 더 많은 평균화, 더 넓은 특징 검출, 단일 픽셀 노이즈에 대한 더 낮은 민감도입니다. 비용은 반경의 제곱에 비례하여 증가합니다 – 5 곱하기 5는 3 곱하기 3의 픽셀당 작업량의 약 2.8배를, 7 곱하기 7은 약 5.4배를 수행합니다 – 그리고 그 배수는 곧바로 프레임 레이트에서 빠져나갑니다.
실용적인 패턴은 표준 카탈로그에 대해서는 size=1 에 머물고, 알고리즘이 더 큰 이웃을 필요로 할 때에만 더 큰 크기를 사용하는 것입니다. 에지 검출기는 3 곱하기 3을 넘어서면 거의 이득이 없고, 스무딩 필터는 때때로 이득이 있습니다. 올바른 크기는 애플리케이션이 강조하거나 억제하려는 특징의 스케일에 따라 달라집니다.
5.16.5. 언제 morph를 사용할 것인가¶
일상적인 스무딩에는 mean(), gaussian(), bilateral() 이 더 빠르고 깔끔합니다. 에지 검출에는 laplacian() 과 find_edges() 가 목적에 맞게 만들어져 있습니다. morph() 를 직접 사용하는 경우는 애플리케이션이 내장 필터가 노출하지 않는 특정 컨볼루션을 필요로 할 때입니다 – 방향성 Sobel, 사용자 정의 에지 템플릿, 파이프라인의 나머지 부분이 찾으려는 특정 텍스처에 맞춰 조정된 커널, 또는 고전적인 이미지 처리가 수십 년에 걸쳐 쌓아 온 유용한 커널들의 표준 카탈로그 중 어느 것이든 말입니다. 임의 커널의 완전한 유연성을 사용할 수 있으며, 그 대가는 애플리케이션이 원하는 결과를 만들어 내는 커널 값을 선택할 책임을 진다는 것입니다.