CrossCompile

Gaussian Blur (OpenCL 없이 CPU 에서 실습)

sh1mj1 2022. 11. 12. 15:26

Gaussian Blur

https://blog.naver.com/PostView.naver?blogId=sees111&logNo=222366804864&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

Gaussian Blur 는 Gaussian Filter 을 이용하여 이미지를 blurring 하는 것이다. blurring 은 마치 초점이 밎지 않은 사진처럼 부드럽게 만드는 필터링 기법이다.

Gaussian Filter 란 Gaussian Distribution 함수를 근사하여 생성한 필터 마스크를 사용하는 필터링 기법이다. 가우시안 분포는 평균을 중심으로 대칭의 종 모양을 가지는 확률 분포이다. 아래는 x, y 좌표에서 2차원 가우시안 분포를 나타내는 사진이다.

가우시안 필터 마스크 행렬은 중앙부에서 비교적 큰 값을 가지고 사이드로 갈 수록 행렬 원소 값이 0에 가까운 작은 값을 가진다. 이 필터 마스크를 이용하여 마스크 연산을 수행한다는 것은 필터링 대상 픽셀 근처에서 가중치를 크게 주고, 필터링 대상 픽셀과 멀리 떨어져있는 주변부에는 가중치를 조금만 주어서 가중 평균을 구하는 것이다.

즉, 가우시안 필터 마스크가 가중 평균을 구하기 위한 가중치 행렬 역할을 하는 것이다.

Bitmap

우리는 Bitmap Format 을 가우시안 블러링 할 것이다! Bitmap 의 형태를 먼저 정리해봅시다.

BMP 파일 포맷은 비트맵 디지털 그림을 저장하는 데 쓰이는 그림 파일 서식이다. BMP 는 다음의 데이터 블록을 가진다.

BMP 헤더 BMP 파일에 대한 일반 정보 (우리는 14Bytes)
비트맵 정보(DIB Header Info) 비트맵 그림에 대한 자세한 정보(우리는 40 Bytes)
색 팔레트 인덱스 컬러 비트맵에 쓰이는 색의 정의(우리는 고려하지 않을 것임)
비트맵 Pixel Data 화소 대 화소 단위의 실제 그림을 담고 있다.

bmp.c 파일에 read_bmp 함수와 write_bmp 함수를 정의해 주었다.

read_bmp 함수 (bmp.c 소스 파일 내에 있음)

간단하게 설명하면 bitmapImage 라는 메모리 공간에 이미지 데이터를 저장하는 함수이다.

여기서 width 와 height 등의 정보들은 Header 에 저장되어 있다. fread 함수를 통해 스트림으로 헤더 정보를 복사해서 저장하고 있다.

그리고 fseek 이라는 함수로 스트림에서 읽기 시작할 위치를 변경한 후 fread 을 통해 실제 이미지 pixel 데이터가 들어있는 부분은 반복문으로 위치를 변경해가면서 따로 읽어준다. bimap 은 BRG 순서이므로 순서를 변경해주는 과정이 필요하기 때문이다.

https://gpgstudy.com/forum/viewtopic.php?t=21251

(참고)bitmap 을 저장할 때 왜 굳이 BRG 순서(거꾸로)로 저장되게 만들었을까?
1. 인텔 CPU 에서는 역워드 방식을 사용해서 바이트 단위로 역순으로 저장하면 워드나 더블워드 단위로 읽으면 정방향으로 입력됨.
2. CPU 어셈블리어 명령어에서 비교 루틴은 cmp 와 상황별 jmp 명령으로 이루어 지는데 0 비교의 경우는 cmp 없이 상황별 jnp 로만 구현이 가능하기 때문에 이렇게 설계했다고 한다.

write_bmp i(bmp.c 소스 파일)

결과 이미지를 write 하는 함수이다.

그리고 우리는 HWC format 을 사용할 것이다. (Height-Width-Channel) Channel 에는 RGB 값이 들어간다. 즉, 아래처럼 사용한다는 뜻이다.

image 배열에 3개씩 RGB channel 값이 들어간다. 이것을 일일이 설정해줄 것이다.

또한 Bitmap’s Header 가 가지는 데이터들을 구조체로 따로 선언해 줄 것이다. 이는 같은 경로에서 BMPHEADER 라는 이름의 구조체로 ImageProcessing.h 라는 헤더 파일에 C 로 만든다.

ImageProcessing.h

참고로 #ifndef 는 중복 define 을 피하기 위해서 사용하는 코드이다.

BMPHeader 구조체에는 위의 값들은 가진다.

여기서는 bfSize, biWidth, biHeight, biBitCount, biSizeImage 정도가 중요한 속성이다.

실습

우리는 CPU 을 이용하여 Bitmap 이미지 파일을 CPU로 Gaussian Blur 하는 것과 GPU로 Gaussian Blur 하는 것을 해볼 것이다.

실습 준비 파일

이 코드를 정상적으로 실행하기 위해서는 아래의 것들을 먼저 준비해야 한다.

  • OpenCL Headers
  • OpenCL Library (libOpenCL.so 혹은 libGLES_mali.so 파일) 
    • 위 둘은 다음 포스팅에서 OpenCL 로 GPU 디바이스를 사용할 때 사용한다.
  • Cross Compiler(arm-linux-androideabi-gcc)
    • 리눅스 환경에서 Android 보드에 크로스 컴파일하는 컴파일러이다.
  • ADB
  • Bitmap image file

CPU Gaussian Blur

먼저 OpenCL 없이 CPU 로 Gaussian Blur 해봅시다. 아래 코드가 필요합니다.

  • ImageProcessing.h
  • bmp.c
  • lena.bmp
  • GaussianBlur.c
  • Makefile

위에서 ImageProcessing.h 헤더 파일과 bmp.c 소스파일을 설명했다. lena.bmp 는 Gaussian Blurring 을 할 때 항상 사용되는 모델 분 사진 (bitmap 형식)이다.

GaussianBlur.c 소스 파일을 봅시다.

GaussianBlur.c

blurring 할 때 걸리는 시간 측정을 위한 start, end, timer 변수.

bitmap 파일의 헤더 정보를 가져오기 위한 bmpHeader 변수

blur 할 타겟인 image 와 blur 된 이미지를 저장할 blurred_img 변수 을 선언한다.

read_bmp 함수로 lena.bmp 파일을 읽는다. 그 후 gaussian_blur 을 수행한다.

blur 수행된 결과를 blurred_lena.bmp 에 저장한다. gaussian_blur 함수의 구현부는 아래와 같다.

gaussian_blur 함수에서는 단순히 source 이미지의 픽셀 channel 에 mask 되는 값을 각각 red, green, blue 에 해당하는 데이터값에다가 곱한 후 이를 결과 이미지의 픽셀 channel 값으로 저장해주고 있다. 그래서 결과 이 blur 된 pixel 의 channel 값을 위에서 write_bmp 함수를 통해 실제로 결과 이미지에 적용을 해주어야 하는 것이다.

즉, 결과적으로는 아래처럼 될 것이다.

Makfile

CC = arm-linux-androideabi-gcc
ADB=adb

.SUFFIXS : .c .o

CFLAG =  -g -O3
LDFLAGS =  -lm

TARGET=GaussianBlur
TARGET_SRC =$(TARGET).c bmp.c 
TARGET_OBJ =$(TARGET).o bmp.o

all: $(TARGET)

$(TARGET): $(TARGET_OBJ)
	$(CC) -static $(TARGET_OBJ) $(LDFLAGS) -o $(TARGET) 
	echo 
	echo "**** Install:" /data/local/tmp/$(TARGET)"****"
	$(ADB) push $(TARGET) /data/local/tmp
	$(ADB) push lena.bmp /data/local/tmp
	$(ADB) shell chmod 755 /data/local/tmp/$(TARGET)

.c.o : 
	$(CC) $(CFLAG) -c $< -o $@ 

clean:
	rm -f *.o
	rm -f $(TARGET)

Makefile 을 통해 GaussianBlur 실행파일과 이미지 파일을 ADB 로 push 해준다.

make 후 실행하는 과정은 아래와 같다.

(HOST)
make
adb shell

(DEVICE)
LD_LIBRARY_PATH=/vendor/lib/egl
cl /data/local/tmp
./GaussianBlur

컨르롤 C

(HOST)
adb pull /data/local/tmp/blurred_lena.bmp ./
display blurred_lena.bmp

https://soft.plusblog.co.kr/39

여기서 LD_LIBRARY_PATH 는

로더(Loader) 가 공유 라이브러리나 동적 라이브러리를 찾아야 할 때 어떤 경로를 찾아가야 하는지를 지정하는 환경변수이다. 즉, 실행 파일을 찾아가는 PATH 환경변수의 라이브러리 버전이라고 생각하면 된다.

로더가 라이브러리를 로딩할 때 우선적으로 LD_LIBRARY_PATH 에 명시된 경로들을 찾아가면서 공유 라이브러리와 동적 라이브러리 파일을 찾게 되며, 그 다음에 표준 라이브러리 경로인 /lib 와 /usr/lib 을 찾게된다.

CPU Gaussian Blur 결과

CPU 로 Gaussian Blur 을 수행했을 때는 4028 만큼의 시간이 걸리는 것을 알 수 있다.

아래는 원래 이미지. (lena.bmp)

아래는 gaussian blur 된 이미지이다. (blurred_lena.bmp )

아래는 CPU Gaussian Blur  코드 깃허브 링크이다.

https://github.com/sh1mj1/GaussianBlur_OpenCL

 

GitHub - sh1mj1/GaussianBlur_OpenCL

Contribute to sh1mj1/GaussianBlur_OpenCL development by creating an account on GitHub.

github.com

 

다음 실습으로는 OpenCL 을 이용하여 GPU 로 Gaussian Blur 을 하는 것을 구현해 볼 것이다!!