프로그래밍

c언어/c++ 이동평균필터

업글 2020. 12. 13. 20:56

안녕하세요 업글입니다! 이번에는 이동평균필터에 대해서 설명드리겠습니다.

이동평균필터는 이동평균필터 외에도 이동평균법, Moving Average 등으로 표현되곤합니다.

 

이동평균필터에 대한 설명

이동평균필터란 아래의 그림과 같이 구간을 이동시키면서 구간에 있는 값들의 평균값을 구하여 실제값을 보다 정확하게 추정하는필터입니다.

여기서 이동평균필터 구간의 크기는 3이고 구간을 1개씩 이동시켜가며 평균값을 구하게됩니다.

아래의 그림에서는 처음 60,48,53의 평균을 구하고 다음 1개 이동하여 48,53,59의 평균을 구하고

그다음 1개씩 이동시켜가면서 평균값들을 구하게 됩니다.

 

이동평균필터의 사용 예시

이동평균필터의 경우 어떠한 입력 값을 읽어올 때 보다 정확한 값을 추정하기 위해서 사용하게됩니다.

저의 경우 주로 센서의 출력 값이나 신호, 모터의 속도 등을 입력받을 때 사용하였습니다.

 

냉장고를 예를 들어서 설명 드리도록 하겠습니다.

냉장고는 냉장고 내부의 온도를 사용자가 설정한 온도로 유지 되도록 합니다.

설정한 온도로 정확하게 유지하기 위해서는 현재 온도 값을 측정하여야 합니다.

설정한 온도보다 측정한 온도가 낮은 경우 열을 가하여 온도를 높여주고

설정한 온도보다 측정한 온도가 높은 경우 냉각을 하여 온도를 낮혀주어야 보다 정확하게 유지할 수 있을 것입니다.

이 때 온도센서나 특정한 회로에서 출력되는 온도 값을 이용하여 냉장고 내부의 온도를 측정할 것입니다.

(온도센서나 특정 회로에서 출력되는 전압 값을 읽어서 온도로 변환하지만 이 부분은 생략하겠습니다.)

 

이동평균필터 적용 결과

이동평균필터를 적용한 결과를 그림을 통해서 보여드리겠습니다.

 

이동평균필터 결과

위의 그림에서 파란색은 로우데이터인 센서값을 나타냅니다. 여기서 실제 값은 50이고 로우데이터는 실제 값에서 -10 ~ 10까지의 오차를 가지게됩니다. 주황색은 이동평균의 구간을 10으로 적용한 결과이고 회색은 이동평균의 구간을 50으로 적용한 결과입니다. 그림에서 보시는바와 같이 주황색의 경우 10개 이후 부터 이동평균 값이 실제 값과 유사하며 회색의 경우 50개 이후 부터 이동평균 값이 실제 값과 유사합니다.

초기에 이동평균필터 배열의 값이 모두 0이고 센서값이 1개씩 들어올 때마다 배열의 값이 1개씩 채워지기 때문입니다. 여기서 이동평균필터 배열의 크기는 구간의 크기와 같습니다. 즉 구간이 10인 경우 배열의 크기는 10이 되게 됩니다.

 

이동평균필터의 구간크기가 50인 경우 10인 경우보다 보다 정확한 값(50)을 추정한다는 것을 확인하실수 있습니다. 이를 통해서 이동평균필터의 구간 크기가 크면 보다 정확하게 추정 가능하니 구간 크기는 크게 가져가면 되겠다라고 생각하실 수 있습니다. 예상하신분들도 계시겠지만 이동평균필터의 구간 크기가 커지게 되면 입력 값에 대해서 지연이 생기게 됩니다. 실제 값은 변하였지만 이동평균필터의 결과 값은 이전 값들을 평균하기 때문에 실제 값에 늦게 반응하게 됩니다.

 

아래의 그림을 통해서 이동평균필터에 의해서 지연되는 것을 확인하실 수 있습니다.

이동평균필터 지연

50번째까지 센서값은 50을 출력하며 50부터 150까지 10씩 증가하며 150에서 250까지 10씩 감소 하도록 하였습니다.

그림에서 이동평균필터의 출력 값이 실제 센서 값과 오차를 가지며 지연을 가진다는 것을 확인하실 수 있습니다.

특히나, 이동평균필터 구간의 크기가 50인 경우 10일때 보다 큰 오차와 지연을 가진다는 것을 확인하실 수 있습니다.

 

그러므로 입력되는 값, 즉 신호의 특성에 따라서 이동평균필터의 구간 크기를 적절하게 설정해주어야 합니다.

이동평균필터 코드

1) 일반적인 이동평균필터

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define FALSE (0)
#define TRUE (!FALSE)

typedef struct {
	int32_t* buffer;
	int32_t sum;
	uint32_t length;
	uint32_t currentIdx;
}ST_MovingAverage;

int32_t MovingAverageInit(ST_MovingAverage* movingAverage, uint32_t length)
{
	int32_t result;

	movingAverage->currentIdx = 0;
	movingAverage->sum = 0;
	movingAverage->length = length;

	movingAverage->buffer = (int32_t*)malloc(length * sizeof(int32_t));

	if (movingAverage->buffer == NULL) {
		result = FALSE;
	}
	else {
		memset(movingAverage->buffer, 0, length * sizeof(int32_t));
		result = TRUE;
	}

	return result;
}

void MovingAverageDelete(ST_MovingAverage* movingAverage)
{
	free(movingAverage->buffer);
}

int32_t MovingAverage(ST_MovingAverage* movingAverage, int32_t value)
{
	int32_t result;

	movingAverage->sum -= movingAverage->buffer[movingAverage->currentIdx];
	movingAverage->sum += value;
	movingAverage->buffer[movingAverage->currentIdx] = value;

	movingAverage->currentIdx++;
	movingAverage->currentIdx %= movingAverage->length;

	result = movingAverage->sum / movingAverage->length;

	return result;
}

int main()
{
	int32_t InitResult = 0;
	int32_t sensorOutput = 0;
	ST_MovingAverage movingAverage[2];

	InitResult = MovingAverageInit(&movingAverage[0], 10);
	if (InitResult == FALSE) {
		printf("Fail MovingAverage[0] Init\n");
		return 0;
	}
	InitResult = MovingAverageInit(&movingAverage[1], 50);
	if (InitResult == FALSE) {
		printf("Fail MovingAverage[1] Init\n");
		return 0;
	}
	
	for (int i = 0; i < 100; i++) {
		sensorOutput = 50 + (rand() % 21 - 10);
		
		printf("%d, %d, %d, %d\n", i, sensorOutput, MovingAverage(&movingAverage[0], sensorOutput), MovingAverage(&movingAverage[1], sensorOutput));
	}

	MovingAverageDelete(&movingAverage[0]);
	MovingAverageDelete(&movingAverage[1]);

	return 0;
}

 

2) 속도 최적 이동평균필터

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define FALSE (0)
#define TRUE (!FALSE)

typedef struct {
	int32_t* buffer;
	int32_t sum;
	uint32_t length;
	uint32_t currentIdx;
	uint32_t divideFactor;
}ST_MovingAverage;

int32_t PowerOf2Check(uint32_t length)
{
	int32_t result;
	uint32_t value, remainder;

	result = TRUE;
	value = length;
	remainder = 0;

	if (value == 0 || value == 1) {
		result = FALSE;
	}
	else {
		while (value != 1) {
			remainder = value % 2;
			if (remainder != 0) {
				result = FALSE;
				break;
			}
			value = value / 2;
		}
	}

	return result;
}

uint32_t GetDivideFactor(uint32_t length)
{
	uint32_t result, value;

	result = 0;
	value = length;

	while (value != 1) {
		value = value / 2;
		result++;
	}

	return result;
}

int32_t MovingAverageInit(ST_MovingAverage* movingAverage, uint32_t length)
{
	int32_t result = TRUE;

	do {
		if (PowerOf2Check(length) == FALSE) {
			result = FALSE;
			break;
		}

		movingAverage->divideFactor = GetDivideFactor(length);

		movingAverage->currentIdx = 0;
		movingAverage->sum = 0;
		movingAverage->length = length;

		movingAverage->buffer = (int32_t*)malloc(length * sizeof(int32_t));
		if (movingAverage->buffer == NULL) {
			result = FALSE;
			break;
		}

		memset(movingAverage->buffer, 0, length * sizeof(int32_t));

	} while (0);
	
	return result;
}

void MovingAverageDelete(ST_MovingAverage* movingAverage)
{
	free(movingAverage->buffer);
}

int32_t MovingAverage(ST_MovingAverage* movingAverage, int32_t value)
{
	int32_t result;

	movingAverage->sum -= movingAverage->buffer[movingAverage->currentIdx];
	movingAverage->sum += value;
	movingAverage->buffer[movingAverage->currentIdx] = value;

	movingAverage->currentIdx++;

	// same : movingAverage->currentIdx %= movingAverage->length;
	movingAverage->currentIdx &= (movingAverage->length - 1);

	// same : result = movingAverage->sum / movingAverage->length;
	result = movingAverage->sum >> movingAverage->divideFactor;

	return result;
}

int main()
{
	int32_t InitResult = 0;
	int32_t sensorOutput = 0;
	ST_MovingAverage movingAverage[2];

	InitResult = MovingAverageInit(&movingAverage[0], 16);
	if (InitResult == FALSE) {
		printf("Fail MovingAverage[0] Init\n");
		return 0;
	}
	InitResult = MovingAverageInit(&movingAverage[1], 32);
	if (InitResult == FALSE) {
		printf("Fail MovingAverage[1] Init\n");
		return 0;
	}
	
	for (int i = 0; i < 100; i++) {
		sensorOutput = 50 + (rand() % 21 - 10);
		
		printf("%d, %d, %d, %d\n", i, sensorOutput, MovingAverage(&movingAverage[0], sensorOutput), MovingAverage(&movingAverage[1], sensorOutput));
	}

	MovingAverageDelete(&movingAverage[0]);
	MovingAverageDelete(&movingAverage[1]);

	return 0;
}

속도 최적화 이동평균필터의 경우, 이동평균필터 구간의 크기가 2의 제곱인 수에만 적용가능하다는점 주의바랍니다.

'프로그래밍' 카테고리의 다른 글

c언어/c++ 속도 최적화 메디안필터  (0) 2020.12.16
c언어/c++ 메디안필터  (0) 2020.12.14
c언어/c++ 함수포인터  (0) 2020.11.26
c언어/c++ sizeof  (0) 2020.11.12
쓰레드(Thread)에 대한 생각  (0) 2020.11.12