티스토리 뷰

함수를 구현하는 방법에는 여러 가지 방법이 있을 수 있다. 그 방법에 따라서 알고리즘의 성능이 달라질 수 있는데, 특별한 상황에서는 정확도가 떨어지더라도 더 빨리 계산을 해야 하는 상황이 있을 수 있다. (예로 자율주행차량에 사용되는 센서의 경우 빠른 처리가 뒷받침이 되어야겠지만, 스마트폰에서 사용하는 사진 보정 app의 경우 처리 속도는 약간 느리더라도 사용자가 마음에 드는 결과를 출력하는 것이 더 중요할 수 있다.) 비슷하게 내가 구현한 프로그램의 성능을 테스트해야 하는 상황이 있는데, 이번에는 openCV 제공하는 방식을 통해 프로그램의 실행 속도를 측정해본다.

 

이전 글을 통해서 color reduction 함수 예제를 기존 방식과 좀 더 효율적인 bitwise operation을 통한 방식으로 구현해보았는데, 실제로 어느 정도의 차이가 나는지 알아본다. 

 

본론에 앞서 우리가 사용하는 컴퓨터나 스마트폰 등과 같은 하드웨어에서 동작은 clock을 기반으로 동작한다. clock이 뛸 때마다 명령어가 실행되는데, 이것은 우리가 작성한 코드도 동일하다. 따라서 우리가 수행하는 프로그램이 동작하는 동안에 clock의 횟수를 알고, 이 clock의 주기만 알 수 있다면 실제로 우리가 익숙한 시간으로 변환할 수 있다. 

 

openCV에서는 cv::getTickCount()가 있다. 이 함수를 시간을 측정하고자하는 코드 전후에 실행한 후 차이를 구하면 뛴 clock의 횟수를 알 수 있다.

 

cv::getTickFrequency() 함수를 통해서는 CPU의 frequency를 알 수 있다. 

 

위 두 함수를 사용하여 코드의 실행 시간을 측정하는 코드를 작성하면 다음과 같다.

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void colorReduce_original(Mat image, int div = 64) {

	int nl = image.rows;	// number of iines
	// total number of elements per line
	int nc = image.cols * image.channels();

	for (int j = 0; j < nl; j++) {
		// get the address of row j
		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++) {
			// process each pixel
			data[i] = data[i] / div * div + div / 2;
			// end of pixel processing
		}
	}
}

void colorReduce_bitWise(cv::Mat image, int div = 64) {
	int nl = image.rows; // number of lines
	// total number of elements per line
	int nc = image.cols * image.channels();
	
	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0) + 0.5);
	// mask used to round the pixel value
	uchar mask = 0xFF << n; // e.g. for div=16, mask= 0xF0
	uchar div2 = div >> 1; // div2 = div/2
	// this loop is executed only once
	// in case of continuous images
	for (int j = 0; j < nl; j++) {
		uchar* data = image.ptr<uchar>(j);
		for (int i = 0; i < nc; i++) {
			*data &= mask;
			*data++ += div2;
		} // end of line
	}
}

void colorReduce_1D(cv::Mat image, int div = 64) {
	int nl = image.rows; // number of lines
	// total number of elements per line
	int nc = image.cols * image.channels();
	if (image.isContinuous()) {
		// then no padded pixels
		nc = nc * nl;
		nl = 1; // it is now a 1D array
	}

	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0) + 0.5);
	// mask used to round the pixel value
	uchar mask = 0xFF << n; // e.g. for div=16, mask= 0xF0
	uchar div2 = div >> 1; // div2 = div/2
	// this loop is executed only once
	// in case of continuous images
	for (int j = 0; j < nl; j++) {
		uchar* data = image.ptr<uchar>(j);
		for (int i = 0; i < nc; i++) {
			*data &= mask;
			*data++ += div2;
		} // end of line
	}
}


int main() {
	// read the image
	Mat image = imread("castle.jpg");
	Mat image2 = imread("castle.jpg");
	Mat image3 = imread("castle.jpg");

	int64 start_original = getTickCount();
	colorReduce_original(image, 64);
	int64 end_original = getTickCount();
	double time_original = (end_original - start_original) / getTickFrequency();
	cout << "original time : " << time_original << endl;

	int64 start_bitWise = getTickCount();
	colorReduce_bitWise(image2, 64);
	int64 end_bitWise = getTickCount();
	double time_bitWise = (end_bitWise - start_bitWise) / getTickFrequency();
	cout << "bitWise time : " << time_bitWise << endl;

	int64 start_1D = getTickCount();
	colorReduce_1D(image3, 64);
	int64 end_1D = getTickCount();
	double time_1D = (end_1D - start_1D) / getTickFrequency();
	cout << "1D time : " << time_1D << endl;

	system("pause");
}

결과는 다음과 같다.

위의 경우 어느정도 이상적인 결과가 나왔다. 일반적인 방법보다 bitWise를 사용한 결과가 더 빨랐으며, 2D -> 1D로 변환 후 계산이 조금 더 빨랐다. 하지만 테스트를 해보면 알겠지만, 우리가 생각했던 것과 딱 맞아떨어지지는 않는다. 내 환경에서 진행을 해보니 더 큰 사이즈의 이미지에서는 확연한 결과를 볼 수 있을거라 예상하였지만 결과는 그렇지 않았다. 오히려 bitWise operation의 경우가 더 느렸고, 원인을 분석해보고 다양한 시도는 해보았지만 정확한 원인은 아직 발견은 못했다. 아마 캐시 등의 사용 여부로 달라지지 않을까.. 생각이 든다. 

 

아무튼 결론으로 openCV에서 함수의 실행 속도를 측정해야한다면 위와 같은 방식으로 할 수 있겠으며, 일반적으로 효율적인 코드를 작성하려면 단순하게 코드가 동작하는지 여부만 신경쓸 것이 아니라, 불필요한 코드가 있는지, 한번만 계산하여 사용할 수 있는 값이 여러 번 계산되지 않는지 등을 고려해야 할 것이다. 메모리 접근 측면에서는 2D의 접근보다는 1D로 변환 후 계산하는 것이 빠르다는 것을 기억하자. 또한 random access를 최소화하는 방법이 있는지 고민하여 코드를 작성하는 것이 좋아보인다.

'영상처리 > OpenCV' 카테고리의 다른 글

08. Downsampling  (4) 2019.04.22
07. mean / gaussian filter  (1) 2019.04.18
05. Scanning with iterator  (0) 2019.04.15
04.Scanning with pointer  (0) 2019.04.10
03. Accessing pixel values  (0) 2019.04.07
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함