[MFC/C++] ListBox를 활용한 다중 필터링

Lachi_·2023년 12월 4일
0

mfc

목록 보기
10/16
post-thumbnail

지난번엔 comboBox를 활용해 필터의 종류와 kernel의 크기를 선택해 기본적인 필터링을 진행했다. 하지만 해당 필터링은 단 한 번의 필터링만 가능했기 때문에 복잡한 연산에 어려움이 있었다. 예를들어, Sharpen Filter를 적용한 후에 Low-Pass Filter를 적용하는 등의 중첩이 불가능했다. 이를 해결하기 위해 ListBox를 사용한다.

기본적인 UI는 다음과 같이 설계했다.

왼쪽 ListBox에는 필터들의 종류가 나열될 것이고, 이를 더블클릭하면 필터가 적용됨과 동시에 오른쪽 ListBox에 선택한 필터들의 목록이 나열될 것이다.

또한 오른쪽 ListBox의 목록들을 더블클릭하면 지워지고, 이미지 또한 필터링 전으로 돌아갈 것이다.

어느정도의 자료구조적인 부분이 들어갈 것 같았기에 여러 함수로 나누어 진행했다.

우선 기존과 다르게 filter랑 kernel을 묶어서 따로 함수로 표시했다.

void CImageDlg::MedianFilter3x3()
{
	filterSize = 3; // 필터 크기를 3으로 설정
	MedianFilter(); // 중간값 필터링 수행
}

void CImageDlg::MedianFilter5x5()
{
	filterSize = 5; // 필터 크기를 5으로 설정
	MedianFilter(); // 중간값 필터링 수행
}


void CImageDlg::LowPassFilter3x3()
{
	filterSize = 3;
	LowPassFilter();
}

void CImageDlg::LowPassFilter5x5()
{
	filterSize = 5;
	LowPassFilter();
}

void CImageDlg::HighPassFilter3x3()
{
	filterSize = 3;
	HighPassFilter();
}

void CImageDlg::HighPassFilter5x5()
{
	filterSize = 5;
	HighPassFilter();
}

void CImageDlg::MinFilter3x3()
{
	filterSize = 3;
	MinFilter();
}

void CImageDlg::MinFilter5x5()
{
	filterSize = 5;
	MinFilter();
}

void CImageDlg::MaxFilter3x3()
{
	filterSize = 3;
	MaxFilter();
}

void CImageDlg::MaxFilter5x5()
{
	filterSize = 5;
	MaxFilter();
}

void CImageDlg::LaplacianFilter3x3()
{
	filterSize = 3;
	LaplacianFilter();
}

void CImageDlg::LaplacianFilter5x5()
{
	filterSize = 5;
	LaplacianFilter();
}

void CImageDlg::SharpenFilter3x3()
{
	filterSize = 3;
	SharpenFilter();
}

void CImageDlg::SharpenFilter5x5()
{
	filterSize = 5;
	SharpenFilter();
}

이어서 이 함수들을 ListBox에 나열했다.

더블클릭해 ListBox에 있는 필터를 적용하는 함수

void CImageDlg::OnLbnDblclkListMenu()
{
	CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_MENU);
	int nIndex = pListBox->GetCurSel();

	if (nIndex != LB_ERR) {
		CString strFilter;
		pListBox->GetText(nIndex, strFilter);

		// 필터링된 이미지가 있다면, 가장 최신의 이미지를 m_filtered에 복사
		if (!m_filteredImages.empty()) {
			memcpy(m_filtered, m_filteredImages.back(), IMAGE_SIZE);
		}
		else {
			// 필터링된 이미지가 없다면, 원본 이미지를 m_filtered에 복사
			memcpy(m_filtered, m_original, IMAGE_SIZE);
		}

		// 선택된 필터 실행
		(this->*filterFuncs[strFilter])();

		// 실행된 필터의 이름을 IDC_LIST_SHOW ListBox에 추가
		CListBox* pShow = (CListBox*)GetDlgItem(IDC_LIST_SHOW);
		pShow->AddString(strFilter);

		// 필터링된 이미지를 m_filteredImages에 추가
		BYTE* filteredImage = new BYTE[IMAGE_SIZE];
		memcpy(filteredImage, m_filtered, IMAGE_SIZE);
		m_filteredImages.push_back(filteredImage);

		// 적용된 필터를 m_appliedFilters에 추가
		m_appliedFilters.push_back(strFilter);

		// 필터링된 이미지 출력
		OutputImage();
	}
}

이미지를 출력하는 함수

void CImageDlg::OutputImage()
{
	// 비트맵 오브젝트가 이미 존재하면 삭제
	if (m_hBitmap != NULL) {
		DeleteObject(m_hBitmap);
		m_hBitmap = NULL;
	}

	// 새로운 비트맵 오브젝트 생성
	m_hBitmap = CreateBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, 1, 8 * 3, m_filtered);


	if (m_memDC2 == NULL)
	{
		m_memDC2 = new CDC;
		m_memBit2 = new CBitmap;

		CClientDC dc(this);
		m_memDC2->CreateCompatibleDC(&dc);
		m_memBit2->CreateCompatibleBitmap(&dc, m_rtViewer2.Width(), m_rtViewer2.Height());
		m_memDC2->SelectObject(m_memBit2);
		m_memDC2->SetStretchBltMode(4);
	}

	CClientDC dc(this);

	StretchDIBits(m_memDC2->GetSafeHdc(),
		0, 0, m_rtViewer2.Width(), m_rtViewer2.Height(), 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT,
		m_filtered, bmpInfo, DIB_RGB_COLORS, SRCCOPY);

	InvalidateRect(m_rtViewer2, FALSE);
}

오른쪽 ListBox에 추가된 내용들을 선택하는 함수

void CImageDlg::OnLbnDblclkListShow()
{
	CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_SHOW);
	int nIndex = pListBox->GetCurSel();

	if (nIndex != LB_ERR) {
		CString strFilter;
		pListBox->GetText(nIndex, strFilter);

		// 선택된 필터 해제
		UnapplyFilter(nIndex, strFilter);

		// ListBox에서 아이템 제거
		pListBox->DeleteString(nIndex);
	}
}

선택한 필터를 해제하고, ListBox에서 제거하는 함수

void CImageDlg::UnapplyFilter(int index, CString strFilter)
{
	// 해당 필터 이전 단계의 이미지로 복구
	if (index > 0) {
		copy(m_filteredImages[index - 1], m_filteredImages[index - 1] + IMAGE_SIZE, m_filtered);
	}
	else {
        if (m_originalImage.IsNull()) {
            AfxMessageBox(_T("오류가 있어서 첫항목을 지우면 동작하지 않습니다."));
            return;
        }
        memcpy(m_filtered, m_originalImage.GetBits(), IMAGE_SIZE);
    }

	// 선택한 필터 제거
	m_appliedFilters.erase(m_appliedFilters.begin() + index);

	// 선택한 필터 이후의 모든 필터 다시 적용
	for (int i = index; i < m_appliedFilters.size(); ++i) {
		(this->*filterFuncs[m_appliedFilters[i]])();
	}

	// 필터링된 이미지 목록에서 해당 이미지 제거
	delete[] m_filteredImages[index];
	m_filteredImages.erase(m_filteredImages.begin() + index);

	// 필터링된 이미지 출력
	OutputImage();
}

이렇게 진행하면서 각 필터들도 중복해서 사용될 수 있기에 기존에 사용된 m_original대신 m_filtered라는 변수로 사용해, 필터링된 이미지에 중복해서 필터를 적용할 수 있게 했다.

출력 결과는 다음과 같다.

라플라시안 5x5 필터를 세 번이나 중첩해 사용하고, 마지막엔 메디안 3x3 필터를 적용했다. 그럼 뒤에서부터 선택해 필터를 제거해보자.

원래 우리가 알던 라플라시안 필터가 한 번만 적용된 모습으로 돌아간 모습이다.
다만 코드에 문제가 있어, 삭제하는 순서가 크게 중요하지 않지만, 맨 첫 번째 선택한 필터를 선택하면 프로그램이 고장났다. 우선 이 부분에 대해서는 따로 예외처리를했는데, 이유를 찾아봐야할 것 같다.


원인을 찾았다.

문제 해결

1. 메모리 누수

디버깅 시 Memory Leak 현상이 발생했다. 이에 대해 delete[]를 통해 동적 메모리 할당된 영역에 대한 메모리 해제가 이루어졌고, 이후 디버깅 시에 Memory Leak detect 현상이 사라졌다.

// m_filteredImages에 추가한 이미지들 해제
	for (BYTE* filteredImage : m_filteredImages)
	{
		delete[] filteredImage;
	}
	m_filteredImages.clear();

	// m_originalImageBytes 해제
	if (m_originalImageBytes != nullptr)
	{
		delete[] m_originalImageBytes;
		m_originalImageBytes = nullptr;
	}

2. ListBox 항목 삭제시의 문제

리스트박스 내의 항목을 삭제하는 UnapplyFilter에서 ListBox의 맨 처음 항목을 삭제했을 때 디버그 오류가 나서 충돌나는 문제가 있었다. 이에 따라 필터가 적용됐을 때의 이미지들을 임시 버퍼에 저장하고, 필터링 된 이미지 목록에서 해당 이미지를 제거하는 작업을 거쳤다.

마지막으로 모든 필터가 제거된 후에는 원본 이미지를 m_filtered에 설정함으로써 문제를 해소했다.

void CImageDlg::UnapplyFilter(int index, CString strFilter)
{
	// 해당 필터 이전 단계의 이미지로 복구
	if (index > 0) {
		copy(m_filteredImages[index - 1], m_filteredImages[index - 1] + IMAGE_SIZE, m_filtered);
	}
	else {
		// 첫 번째 필터를 제거하는 경우 m_originalImageBytes를 m_filtered로 복사
		memcpy(m_filtered, m_originalImageBytes, IMAGE_SIZE);
	}

	// 선택한 필터 제거
	m_appliedFilters.erase(m_appliedFilters.begin() + index);

	// 필터링된 이미지 목록에서 해당 이미지 제거
	delete[] m_filteredImages[index];
	m_filteredImages.erase(m_filteredImages.begin() + index);

	// 선택한 필터 이후의 모든 필터 다시 적용
	BYTE* tempImage = new BYTE[IMAGE_SIZE];
	memcpy(tempImage, m_filtered, IMAGE_SIZE);
	for (int i = index; i < m_appliedFilters.size(); ++i) {
		memcpy(m_filtered, tempImage, IMAGE_SIZE);
		(this->*filterFuncs[m_appliedFilters[i]])();
		memcpy(tempImage, m_filtered, IMAGE_SIZE);
	}
	delete[] tempImage;

	// 모든 필터가 제거된 후에는 원본 이미지를 m_filtered에 설정
	if (m_filteredImages.empty()) {
		memcpy(m_filtered, m_originalImageBytes, IMAGE_SIZE);
	}

	// 필터링된 이미지 출력
	OutputImage();
}
profile
개인 저장용. 오류 매우 많음.

0개의 댓글