Tài trợ bài viết này và giới thiệu dịch vụ, sản phẩm, thương hiệu, nhu cầu tuyển dụng của doanh nghiệp đến với cộng đồng.
Kim Uyên Bài viết trình bày thuật giải làm mờ ảnh với hàm gaussian. Giải thuật được hiện thực hoá sử dụng ngôn ngữ C++ có hỗ trợ của thư viện OpenCV. Đồng thời, tạo giao diện đơn giản sử dụng Qt, hỗ trợ các bạn trong quá trình demo chương trình trên lớp.
Nội dung bài viết

Giới thiệu

Bài viết trình bày thuật giải làm mờ ảnh với hàm gaussian. Giải thuật được hiện thực hoá sử dụng ngôn ngữ C++ có hỗ trợ của thư viện OpenCV. Đồng thời, tạo giao diện đơn giản sử dụng Qt, hỗ trợ các bạn trong quá trình demo chương trình trên lớp. Tạo giao diện đơn giản để demo trên lớp học, để báo cáo đồ án là điều sinh viên muốn thực hiện khi mà project có quá nhiều tác vụ.

Tiền đề bài viết

Bài viết bắt nguồn từ yêu cầu của một độc giả STDIO. Tôi viết bài viết này hy vọng giải quyết được vấn đề bạn gặp phải.

Đối tượng hướng đến

Các lập trình viên có kiến thức cơ bản về C++, xử lý ảnh và OpenCV.

Để có thể hiện thực hoá bằng mã nguồn, bạn cần hiểu tổng quát về bài toán, tôi đưa ra giải thuật dưới đây cho việc xử lý bài toán lọc gaussian.
Trước khi đi vào giải thuật, bạn cần đọc 2 bài viết dưới đây:

Giới Thiệu Ứng Dụng Của Làm Mờ Ảnh (Lọc Nhiễu) Trong Bài Toán Nhận Dạng: Bài viết giúp bạn có được lý thuyết về bài toán lọc nhiễu và phương pháp xử lý xử dụng toán tử gaussian.

Phép Tích Chập Trong Xử Lý Ảnh (Convolution): Do trong bài viết này tôi sử dụng kỹ thuật tích chập để giải quyết bài toán lọc gaussian.

Chúng ta bắt đầu đi vào giải thuật sau khi hoàn thành việc đọc hiểu 2 bài viết trên.

Thuật giải lọc Gaussian

Ký hiệu:

  • Ảnh gốc: _srcImg. Kích thước: RI x CI.
  • Kernel: _kernel. Là mảng một chiều có số phần tử RK x CK.
  • Độ lệch chuẩn của hàm gauss: sigma.

Bước 1:

  • Tính toán ma trận kernel của toán tử gaussian theo công thức.

ss_1

  • Tính chỉ số truy cập nhanh _kernelIndex, sử dụng trong Tích Chập. Mỗi phần tử là một Point với x: chỉ số dòng, y: chỉ số cột. Anchor point có toạ độ (0, 0).

Bước 2:

  • Tính tổng giá trị của tất cả các phần tử trong ma trận kernel lưu vào sumKer. Dành cho việc chuẩn hoá giá trị của mỗi phần tử trong kernel.
sumKer := sum(_kernel);
IF  sumKer == 0:
    sumKer := 1;
  • Chuẩn hoá: chia mỗi phần tử trong _kernel cho sumKer:
_kernel.element := _kernel.element / sumKer;

Bước 3: Thực hiện convolution _kernel với _srcImg cho ảnh kết quả _destImg:

For each pixel pS in _srcImg:
    For each element eK in _kernel:
        _destImg[pS] += _kernel[eK] * _srcImg[pS - _kernelIndex[eK]];
    End For
End For

Hiện thực giải thuật

Trong quá trình hiện thực giải thuật, một số bước tôi không tuân theo, do nghĩ đến thời gian thực thi của chương trình.

Như trong bước 2, tôi không thực hiện chuẩn hoá kernel ngay. Mà thực hiện tích chập trước, kết quả tích chập sẽ đem chia cho sumKer.

Tính toán ma trận kernel:

/*
* @Brief:		function to create kernel for gaussian filter. With height, width, sigma input by users.
* @Param[out]:	kernel: vector with size = 0.
* @Param[in]:	height: Numbers of row of matrix kernel will be created. Should: Odd value.
* @Param[in]:	width: Numbers of column of matrix kernel will be created. Should: Odd value.
* @Param[in]:	sigma: Standard deviation of gaussian funct.
* @Retval:		None.
*/
void
TEST::createGaussianKernel(vector<float> & kernel, int height, int width, float sigma)
{
	float		inverse_Sqrt2Pi_Sigma;
	float		inverse_2_SigmaSquare;
	int			indexRow, indexCol;

	inverse_Sqrt2Pi_Sigma = 1 / (sqrt(2 * PI) * sigma);
	inverse_2_SigmaSquare = 1 / (2 * sigma * sigma);

	for (indexRow = -height / 2; indexRow < ((height - 1) / 2) + 1; indexRow++)
	{
		for (indexCol = -width / 2; indexCol < ((width - 1) / 2) + 1; indexCol++)
		{
			kernel.push_back(inverse_Sqrt2Pi_Sigma
				* exp(-(indexRow * indexRow + indexCol * indexCol)
				* inverse_2_SigmaSquare
				));
		}
	}
}

Tính toán _kernelIndexsumKer:

for (int i = -(_kernelHeight / 2); i < ((_kernelHeight - 1) / 2) + 1; i++)
		for (int j = -(_kernelWidth / 2); j < ((_kernelWidth - 1) / 2) + 1; j++)
		{
			_kernelIndex.push_back(Point(i, j));
			_sumKernel += _kernel[ii++];
		}
	
	if (_sumKernel == 0)
		_sumKernel = 1;

Thực hiện convolution _kernel với _srcImg cho ảnh kết quả _destImg:

void Convolution::doConvolution(Mat& sourceImage, Mat& destinationImage)
{
	int numRowImg = sourceImage.rows;
	int numColImg = sourceImage.cols;
	int indexRow, indexCol, indexKernel, indexRImg, indexCImg;
	float g_val;
	destinationImage.create(Size(numColImg, numRowImg), CV_8UC1);

	for (indexRow = 0; indexRow < numRowImg; indexRow++)
	{
		uchar* data = destinationImage.ptr<uchar>(indexRow);

		for (indexCol = 0; indexCol < numColImg; indexCol++)
		{
			g_val = 0;

			for (indexKernel = 0; indexKernel < _kernel.size(); indexKernel++)
			{
				indexRImg = indexRow - _kernelIndex[indexKernel].x;
				if (indexRImg < 0 || indexRImg > numRowImg - 1)
					continue;

				indexCImg = indexCol - _kernelIndex[indexKernel].y;
				if (indexCImg < 0 || indexCImg > numColImg - 1)
					continue;

				g_val += _kernel[indexKernel] * sourceImage.at<uchar>(indexRImg, indexCImg);
			}

			data[indexCol] = saturate_cast<uchar> (g_val / _sumKernel);
		}
	}
}

 

Bước tiếp theo, ta đi vào việc tạo giao diện đơn giản để chạy thuật toán này. Thực ra, nếu chỉ chạy thuật toán này thôi thì ta không cần tạo giao diện, chỉ cần OpenCV là đủ. Tuy nhiên, bài viết hướng đến việc bạn sẽ tạo giao diện cho một project của chính bạn với nhiều tác vụ. Những mục tiếp theo sẽ giúp bạn làm điều này.

Tạo Project với Qt + OpenCV

Trong bài viết này, tôi sử dụng các phiên bản phần mềm như sau:

  • Windows 10 - 64bit.
  • Visual Studio 2013 community - 32bit.
  • Qt 5.5.0 for Windows 32 bit.
  • OpenCV 3.0.

Cài đặt và tạo project Qt

Bạn đọc tham khảo bài viết: Tạo GUI Trong C++ Sử Dụng Qt.

Cài đặt và cấu hình thư viện OpenCV cho project

Bạn đọc tham khảo bài viết: OpenCV – Cài Đặt Và Ví Dụ Minh Họa Sử Dụng của tác giả Vũ Quang Huy

Sau khi tạo được project Qt và cấu hình sử dụng thư viện OpenCV cho project. Tiếp ta đi thiết kế giao diện:

Thiết kế giao diện

ss_2

Ở cây thư mục project TEST click chọn test.ui và kéo thả như hình dưới:

ss_3

Bạn thực hiện Build, chương trình sẽ tự động write lại các file trong Filter folder Generated Files. Vì vậy, bạn không nên quan tâm đến những file trong thư mục này.

Thiết kế Class

ss_4

Class TEST (test.h, test.cpp):

  • ui: quản lý giao diện. Hàm ui.setUI được gọi trong hàm khởi tạo của class TEST.
  • _sourceImg: lưu ảnh gốc.
  • _destImg: lưu ảnh sau khi làm mờ.
  • createGaussianKernel: hàm tạo ma trận kernel với kích thước và độ lệch chuẩn do người dùng định.
  • connectObject: hàm thực hiện kết nối tất cả các thông qua signals và slots button QObject (ChooseImage và GaussianBlur). Hàm được gọi trong hàm khởi tạo của lớp.
  • slots openImage: được gọi khi người dùng click chọn button Choose Image. Hàm hiển thị dialog để chọn ảnh thực hiện làm mờ.
  • slots doConvo: được gọi khi người dùng click chọn button Gaussian Blur. Hàm thực hiện: createGaussianKernel → Convo.setKernel (đối tượng Convo thuộc lớp Convolution) → Convo.doConvolution → Hiển thị kết quả.

Class Convolution (convolution.h, convolution.cpp): được trình bày như trên giải thuật.

Kết quả

Kết quả thực hiện với kernel(5x5), sigma = 2. Theo thứ tự từ trái qua phải: 1. Ảnh gốc. 2. Ảnh thực hiện bởi thuật giải. 3. Ảnh thực hiện bởi hàm GaussianBlur của thư viện OpenCV.

ss_5

Nếu trong quá trình thực hiện có vấn đề gặp phải hay thắc mắc gì, các bạn có thể post câu hỏi trực tiếp ở bên dưới, hoặc liên hệ với Kim Uyên.

FILE THỰC THI: Release_GaussianBlur_STDIO.zip

THẢO LUẬN
ĐÓNG