2012-06-21 3 views
2

3x3 필터를 사용하여 픽셀 이미지 당 1 비트를 필터링하려고합니다. 각 입력 픽셀에 대해 주변 출력 픽셀의 가중 합계가 1이면 해당 출력 픽셀이 1로 설정됩니다. 필터)가 일부 임계 값을 초과합니다.필터링 1bpp 이미지

나는 이것이 8 bpp로 변환 한 다음 필터링하는 것보다 더 효율적 일 것이라고 기대했지만, 좋은 방법이라고 생각하지 않습니다. 순진한 방법은 바이트에 대한 9 개의 포인터를 추적하는 것입니다 (이 세 바이트의 첫 번째 및 마지막 비트에 대한 출력을 계산하기 위해 각 행에있는 현재 바이트의 양 측면에 대한 세 개의 연속 행 및 포인터). 각 입력 픽셀에 대해 바이트의 가장자리 비트 여분 faff와

sum = filter[0] * (lastRowPtr & aMask > 0) + filter[1] * (lastRowPtr & bMask > 0) + ... + filter[8] * (nextRowPtr & hMask > 0),

. 그러나 이것은 느리고 실제로보기 흉하게 보입니다. 당신은 각 바이트마다 8 픽셀을 가지고 대신에 작업을 마스킹하는 여분의 작업을해야한다는 사실로부터 어떤 병렬성도 얻지 못합니다.

이런 종류의 작업을 수행하는 가장 좋은 방법은 있습니까? 이 특별한 문제에 대한 해결책은 놀랍지 만, C/C++의 1bpp 이미지에 대한 효율적인 이미지 처리의 예를 지적하게되어 기쁩니다. 나는 이미지 변환과 복사를 피하기 위해 앞으로 8 bpp의 것들을 1 bpp 알고리즘으로 대체하고 싶다. 그래서 이것에 대한 일반적인 자료는 감사 할 것이다.

+0

이미지 너비가 8의 배수입니까, 아니면 각 행이 바이트 경계에서 시작되도록 패딩 된 선입니까? 그것은 대개의 경우이며, 상황이 좀 더 쉬워집니다. 9 대신에 3 개의 마스크 만 필요합니다. –

+0

이미지 너비는 임의이지만 각 줄은 바이트 경계에서 시작됩니다. 행은 더 큰 경계에 패딩 될 수 있지만 바이트로 채워질 수 있습니다. – theotherphil

+0

가장자리의 채우기 방법은 무엇입니까? 반복 또는 클램프? – Ani

답변

2

몇 년 전에 바이트를 비트로 풀고 필터를 수행 한 다음 다시 바이트를 비트로 패킹하는 것이 비트를 직접 사용하는 것보다 빠르다는 것을 발견했습니다. 반 직관적 인 것처럼 보입니다. 왜냐하면 1 대신 3 개의 루프가 있기 때문입니다.하지만 각 루프의 단순성은 그 이상으로 구성되어 있습니다.

아직 가장 빠르다고 보장 할 수는 없습니다. 컴파일러 및 특히 프로세서가 변경되기 쉽습니다. 그러나 각 루프를 단순화하면 최적화하기가 쉬울뿐 아니라 읽기가 더 쉬워집니다. 그것은 가치있는 일이되어야합니다.

별도의 버퍼로 압축을 푸는 것의 또 다른 이점은 가장자리에서 수행하는 작업에 유연성을 제공한다는 것입니다. 버퍼를 입력보다 2 바이트 크게함으로써 바이트 1부터 시작하여 바이트 0과 n을 원하는대로 설정하면 필터링 루프가 경계 조건을 전혀 염려 할 필요가 없습니다.

+0

좋습니다, 감사합니다. 나는 언 패킹하지 않고 좀 더 빨리 할 수 ​​있기를 바랬지 만, 기존 코드를 고수하는 것이 가장 좋습니다. 8 bpp 이미지의 코드는 이미 충분히 최적화되어 있으므로 1 bpp 작업으로이 작업을 수행하는 것이 많은 것처럼 들립니다. – theotherphil

1

전체 이미지를 1 비트/바이트 (또는 8bpp, 본질적으로 언급 한대로)로 확장하는 대신 현재 창을 확장하여 첫 번째 행의 첫 번째 바이트를 읽고 이동하고 마스크 한 다음 읽을 수 있습니다 당신이 필요로하는 3 비트; 다른 두 행에 대해서도 똑같이하십시오. 그런 다음 다음 창에서 왼쪽 열을 버리고 각 행에서 하나 더 가져옵니다. 이 작업을 수행하기위한 로직과 코드는 전체 이미지를 단순히 확장하는 것만 큼 쉽지는 않지만 메모리는 훨씬 적게 듭니다.

중간재로서, 현재 작업중인 3 개의 행을 확장 할 수 있습니다. 아마도 그렇게 쉽게 코딩 할 수 있습니다.

2

분리 가능한 필터를 살펴보십시오. 무엇보다도 그들은 작동하는 곳에서 대규모 병렬 처리를 허용합니다. 3x3로하여 샘플의 중량 및 필터의 경우, 예를 들어

:

  1. 샘플 1 × (가로) 화소 버퍼로. 이는 각 픽셀에 대해 독립적으로 수행 할 수 있으므로 1024x1024 이미지는 1024^2 동시 작업을 실행할 수 있으며이 작업은 모두 3 개의 샘플을 수행합니다.
  2. 버퍼의 샘플 3x1 (수직) 픽셀 . 다시 한번, 이것은 모든 픽셀에서 동시에 수행 될 수 있습니다.
  3. 버퍼 내용을 사용하여 원래 텍스처의 픽셀을 제거합니다.

이 방법의 장점은, 수학적으로는 소스에 동일한 크기의 버퍼를 필요로하지만, 이미 사본을 수행하는 경우가 있음을, (n^2에서 2n에 샘플 작업의 수를 삭감한다는 것입니다 버퍼로 사용할 수 있으며 2 단계에서 원본 소스를 수정할 수 없습니다. 메모리 사용량을 2n으로 유지하려면 2 단계와 3 단계를 함께 수행 할 수 있습니다 (다소 힘들고 완전히 즐겁지는 않습니다). 메모리가 문제가되지 않으면 3n을 두 개의 버퍼 (source, hblur, vblur)에 보낼 수 있습니다.

각 작업이 불변 소스와 완전히 분리되어 작동하기 때문에 코어가 충분하면 모든 픽셀에서 동시에 필터를 수행 할 수 있습니다. 또는보다 현실적인 시나리오에서 페이징 및 캐싱을 활용하여 단일 열이나 행을로드하고 처리 할 수 ​​있습니다. 홀수 스트라이드, 행 끝의 패딩 등으로 작업 할 때 편리합니다. 두 번째 샘플 (세로)은 캐시와 결합 될 수 있지만, 최악의 경우 한 라운드는 캐시 친화적 일 것입니다. 처리를 지수에서 선형으로 자른다.

이제 데이터를 비트 단위로 저장하는 경우에 대해서는 아직 다루지 않았습니다. 그것은 상황을 약간 더 복잡하게 만들지 만 그다지 심각하지는 않습니다. 롤 창을 사용할 수 있다고 가정하면 다음과 같이됩니다.

d = s[x-1] + s[x] + s[x+1] 

이 작동합니다. 흥미롭게도, 1 단계의 출력 (예 : 읽을 때 (y,x)의 샘플)에서 이미지를 90도 회전하려면 모든 샘플에 대해 수평 적으로 인접한 바이트 두 개와 같은 단일 바이트 만로드하면됩니다. 시간의 75 %. 이것은 읽는 동안 캐시와 약간 덜 친숙하지만, 알고리즘을 크게 단순화합니다 (손실을 되찾기에 충분할 정도로).

의사 코드 :

buffer source, dest, vbuf, hbuf; 

for_each (y, x) // Loop over each row, then each column. Generally works better wrt paging 
{ 
    hbuf(x, y) = (source(y, x-1) + source(y, x) + source(y, x+1))/3 // swap x and y to spin 90 degrees 
} 
for_each (y, x) 
{ 
    vbuf(x, 1-y) = (hbuf(y, x-1) + hbuf(y, x) + hbuf(y, x+1))/3 // 1-y to reverse the 90 degree spin 
} 
for_each (y, x) 
{ 
    dest(x, y) = threshold(hbuf(x, y)) 
} 

바이트 내의 비트 액세스는 (source(x, y) 액세스/샘플을 나타냄)을 수행하는 것이 비교적 간단하지만, 통증의 종류는, 여기에서 매우 독자의 몫이다 써내. 이 방식 (90도 회전)으로 구현 된 원리는 각각 n 개의 샘플을 2 회 통과해야하며 항상 바로 인접한 비트/바이트로부터 샘플링합니다 (다음 행의 비트 위치를 계산할 필요가 없음) . 대체로 어떤 방법보다 훨씬 빠르고 간단합니다.

+0

분리 가능한 필터는 거의없고, 3x3의 경우 거의 가치가 없습니다. –

+0

대부분의 상자 필터는 바이트 내에 저장된 비트를 처리하는 경우 다음 번 행에서 임의의 비트를 찾는 것을 피하는 것이 좋습니다. 3x3에서 필터를 분리하는 것에서 얻는 순수한 성능 향상은 작지만 (5x5 정도까지는 그만한 가치가 없습니다), 그러나 여기에 더 도움이됩니다. – ssube

+0

필자는 필터 가중치가 상자 필터가 아니라는 점을 명시 적으로 언급했기 때문에이를 가정했습니다. 가능하다면 각각의 무게가 '1/9'라고 가정합니다 ... –