본문 바로가기
Nuke/Blink Script

blink kernels (4)

by 르면가게 2024. 12. 8.

Random Access

https://learn.foundry.com/nuke/developers/13.2/BlinkKernels/ConvolutionKernel.html

 

Random Access — Guide to Writing Blink Kernels

The ConvolutionKernel The next kernel we’re going to look at does a convolution, or weighted two-dimensional blur. The blur weights are taken from a second input image, filter, so for every output pixel, the kernel needs to access all the pixels from the

learn.foundry.com

The ConvolutionKernel

//경고: 필터 입력에 큰 이미지를 연결하면 커널이 매우 느리게 실행됩니다!
//GPU에서 실행 중이고 화면에 연결된 GPU에서 실행하는 경우, 커널 실행 시간이
//운영 체제가 허용하는 시간보다 길어지면 문제가 발생할 수 있습니다. 주의해서 사용하세요!
kernel ConvolutionKernel :public ImageComputationKernel<ePixelWise>
{
  Image<eRead, eAccessRanged2D, eEdgeClamped> src;
  Image<eRead, eAccessRandom> filter;
  Image<eWrite> dst;

local:
  int2 _filterOffset;

  void init()
  {
	//필터 입력의 크기를 얻고 반지름을 저장합니다.
   int2 filterRadius(filter.bounds.width()/2, filter.bounds.height()/2);

//현재 픽셀에서 필터 이미지의 왼쪽 하단 모서리까지의 오프셋을 저장합니다.
		_filterOffset.x = filter.bounds.x1 - filterRadius.x;
    _filterOffset.y = filter.bounds.y1 - filterRadius.y;

//src 이미지 액세스 설정
		src.setRange(-filterRadius.x, -filterRadius.y, filterRadius.x, filterRadius.y);
  }

  void process() {

    SampleType(src) valueSum(0);
    ValueType(filter) filterSum(0);

	//필터 이미지에서 반복합니다
	for(int j = filter.bounds.y1; j < filter.bounds.y2; j++) {
		for(int i = filter.bounds.x1; i < filter.bounds.x2; i++) {
			//Get the filter value
			ValueType(filter) filterVal = filter(i, j, 0);

			//src 값에 해당 필터 무게를 곱하여 누적합니다
      valueSum += filterVal * src(i + _filterOffset.x, j + _filterOffset.y);

			//Update the filter sum with the current filter value
			filterSum += filterVal;
      }
    }

		//값 합계를 정규화하여 0으로 나눕니다
		if (filterSum != 0)
      valueSum /= filterSum;

    dst() = valueSum;
  }
};

Pixelwise Iteration

셀 단위 처리:

  • ConvolutionKernel은 ImageComputationKernel<ePixelWise>를 상속받아, 출력 이미지를 픽셀 단위로 순차 처리합니다. 즉, 출력 이미지의 각 픽셀을 한 번에 처리하고, 그에 대응하는 필터 이미지의 픽셀만 참조합니다.

필터 이미지의 첫 번째 채널만 사용:

  • 이 커널은 필터 이미지의 첫 번째 채널만 사용합니다. 따라서 필터 이미지의 각 채널을 여러 번 접근할 필요 없이 각 출력 픽셀에 대해 한 번만 필터 이미지를 참조하면 됩니다.

효율성:

  • 픽셀 단위로 처리하는 방식 덕분에, 필터 이미지의 각 픽셀을 각 출력 픽셀마다 한 번만 접근할 수 있어 계산 효율성이 개선됩니다. 이 방식은 특히 필터 이미지의 여러 채널을 사용할 필요가 없을 때 유리합니다.
kernel ConvolutionKernel : public ImageComputationKernel<ePixelWise>

Random Access

ConvolutionKernel은 각 출력 픽셀마다 필터 입력 전체를 참조해야 합니다. 이를 위해 eAccessRandom 접근 방법을 사용합니다. 이 방법은 출력 이미지의 모든 픽셀에서 필터 이미지의 어떤 위치에도 접근할 수 있게 합니다.

핵심 내용:

  1. eAccessRandom 접근 방법
    • eAccessRandom은 필터 이미지의 특정 위치에 무작위로 접근할 수 있게 해줍니다. 이를 통해 각 출력 픽셀을 계산할 때마다 필터 이미지의 전체를 자유롭게 참조할 수 있습니다.
    Image<eRead, eAccessRandom> filter;
    
  2. 엣지 방법 (Edge Method):
    • eAccessRandom 방식으로 입력 이미지를 접근할 때, **엣지 방법 (edge method)**을 지정할 수 있습니다. 이는 eAccessRanged1D나 eAccessRanged2D와 동일한 방식으로 설정 가능합니다.
    • 하지만 이 경우, 필터 입력을 벗어난 픽셀을 접근할 필요가 없으므로 엣지 방법을 따로 지정하지 않습니다.

init() Function

ConvolutionKernel에서 **랜덤 접근 (random access)**을 사용하면, 출력 위치에 상관없이 입력 이미지의 모든 픽셀에 접근할 수 있습니다. 이로 인해 필터 입력에 대한 접근 요구사항을 init() 함수에서 따로 지정할 필요가 없습니다. 그러나, 필터 입력에 대한 정보를 저장하여 src 이미지 접근 시에 도움이 될 수 있습니다.

핵심 내용:

  1. 랜덤 접근 입력의 크기 파악:
    • 랜덤 접근을 사용하면 입력 이미지의 크기 정보를 알 수 있는 bounds 멤버가 제공됩니다. bounds는 recti라는 구조체로, 사각형 영역을 나타내며 정수 좌표를 가집니다.
    • bounds.width()와 bounds.height()를 사용해 필터 입력의 크기를 알 수 있습니다. 이 값을 기반으로 **필터의 반지름 (radius)**을 계산합니다.
    int2 filterRadius(filter.bounds.width() / 2, filter.bounds.height() / 2)
    
  2. 입력의 경계 값:
    • bounds.x1, bounds.y1, bounds.x2, bounds.y2를 사용하여 필터 이미지의 경계를 알 수 있습니다. 이 값들은 **필터 이미지의 하단 왼쪽 모서리로부터의 오프셋 (offset)**을 계산하는 데 사용됩니다.
    // 필터 이미지의 하단 왼쪽 모서리로부터의 오프셋 계산
    _filterOffset.x = filter.bounds.x1 - filterRadius.x;
    _filterOffset.y = filter.bounds.y1 - filterRadius.y;
    
  3. src 이미지에 대한 접근 설정:
    • 계산된 필터의 반지름을 이용해 src 입력 이미지의 **2D 범위 접근 (ranged access)**을 설정합니다. 이때, 필터 반지름을 기반으로 src 이미지의 접근 범위를 정의합니다.
    // src 이미지 접근 범위 설정
    src.setRange(-filterRadius.x, -filterRadius.y, filterRadius.x, filterRadius.y);
    
  4. 범위 오버추정 (Overestimate):
    • 코드에서는 짝수 크기 입력에 대해 접근 범위를 한 칸 더 넓게 설정합니다. 이렇게 설정하면 범위가 실제 필요한 것보다 더 넓게 계산되지만, 중요한 점은 필요한 범위를 과소 추정하여 잘못된 결과가 나오는 것을 방지하는 것입니다. 과소 추정이 일어나면 예기치 못한 결과가 나올 수 있기 때문입니다.

요약:

  • eAccessRandom을 사용하면 입력 이미지의 크기와 경계를 알 수 있으며, 이를 기반으로 필터 반지름과 src 이미지 접근 범위를 계산할 수 있습니다.
  • 경계 정보를 활용하여 필터 입력을 정확하게 처리하고, 오버추정된 범위 설정으로 오류를 방지합니다.

Pixelwise and Random Access from the process() Function

process() 함수 내에서 컨볼루션을 수행하기 위해 필터 이미지를 순회하며 작업을 진행합니다. 이 과정에서 필터는 현재 출력 위치를 중심으로 놓이며, 필터가 커버하는 각 입력 픽셀에 대해 해당 필터 가중치와 곱해지고 이 값을 누적합니다. 필터 가중치가 1로 합산되지 않는 경우, 마지막에 이 값을 보정하여 입력 이미지의 밝기를 유지합니다.

핵심 내용:

  1. 입력 및 필터 값의 가중치 합산:
    • 필터 이미지의 각 픽셀을 순회하며, 각 입력 픽셀의 값을 필터 가중치와 곱하여 누적합니다. 또한, 필터 가중치도 누적합니다. 이 값을 valueSum에 누적하고, 필터 가중치는 filterSum에 누적합니다.
    SampleType(src) valueSum(0);          // 입력 값 누적 변수
    ValueType(filter) filterSum(0);       // 필터 가중치 누적 변수
    
  2. 픽셀 단위 접근 (Pixelwise access):
    • src (입력 이미지)는 픽셀 단위 접근을 사용하여 한 번에 전체 픽셀을 접근할 수 있습니다. 각 픽셀은 SampleType(src)로 정의된 벡터 형태로, 각 성분은 입력 이미지의 각 채널에 해당하는 값입니다.
    • *filter*는 첫 번째 채널만 사용하며, filter(i, j, 0)을 통해 해당 위치의 필터 가중치를 가져옵니다.
    ValueType(filter) filterVal = filter(i, j, 0);  // 필터 값
    valueSum += filterVal * src(i + _filterOffset.x, j + _filterOffset.y);  // 가중치 곱셈 및 누적
    
  3. 반복문을 통한 필터 이미지 순회:
    • 필터 이미지의 크기 (filter.bounds)에 맞춰 이중 반복문을 사용하여 필터의 값을 순차적으로 참조하고 누적합니다.
    for(int j = filter.bounds.y1; j < filter.bounds.y2; j++) {
        for(int i = filter.bounds.x1; i < filter.bounds.x2; i++) {
            // 필터 값과 src 값 곱셈 및 누적
        }
    }
    
  4. 필터 가중치 합산 후 정규화:
    • 필터 이미지의 가중치 합산이 끝나면, 정규화 과정을 통해 입력 값의 밝기를 보정합니다. 가중치 합이 0일 경우 나누기 연산을 방지하기 위해 조건문을 사용하여 제어합니다.
    if (filterSum != 0)
      valueSum /= filterSum;  // 필터 가중치로 정규화
    
  5. 성능 고려:
    • 필터 이미지 크기가 커지면 이 커널의 실행 시간이 급격히 증가할 수 있습니다. 특히 GPU에서 실행할 경우, 실행 시간이 운영 체제의 타임아웃 한도를 초과할 수 있기 때문에 필터 크기를 적당히 유지하는 것이 중요합니다.

결론:

  • process() 함수는 픽셀 단위로 입력 이미지와 필터를 처리하고, 각 입력 픽셀을 필터 가중치와 곱하여 누적합니다. 마지막에 필터 가중치로 정규화하여 입력 이미지의 밝기를 유지하며, 필터 크기가 너무 커지지 않도록 주의해야 합니다.

'Nuke > Blink Script' 카테고리의 다른 글

Blink Scripting 101 - Blink 노드의 구성  (0) 2024.12.11
blink kernels (5) - Blink Reference Guide  (0) 2024.12.08
blink kernels (3)  (0) 2024.12.08
blink kernels (2)  (0) 2024.12.08
blink kernels (1)  (0) 2024.12.08