Search…

Blur - Ý Tưởng và Giải Thuật Làm Mờ Ảnh Đơn Giản

10/11/20207 min read
Hiểu về phương pháp làm mờ ảnh cơ bản với C++ thông qua ví dụ và code mẫu.

Làm mở ảnh còn có thể có những tính ứng dụng như lọc nhiễu trong xử lý ảnh, xem thêm:

Blur hay ảnh mờ là gì?

Quan sát 2 hình bên dưới và nhận xét thấy rằng, hình thứ 2 nhòe (mờ) hơn hình 1.

Hình 1: rõ nét
Hình 2: mờ (blur)

* Ảnh được lấy từ Bugs Store.

Nguyên lý của việc làm mờ ảnh

Tại một tọa độ pixel xác định trên ảnh kết quả, nó chính là việc giao thoa màu với các điểm ảnh lân cận.

Xét ảnh gốc với kích thước với width = 3px và height = 3px

Điều mong đợi là làm mờ ảnh trên, công việc là tính toán lại màu cho 9 pixel (0:0, 0:1, 0:2, 1:0, 1:1, 1:2, 2:0, 2:1, 2:2) trên dựa phương pháp trung bình cộng của các pixel màu gốc với các pixel xung quanh nó.

Tại vị trí 0:0 thì kết quả màu sẽ là trung bình cộng màu của các vị trí 0:0, 0:1, 1:0, 1:1 chia cho 4 (1 điểm gốc và 3 điểm lân cận).

Các điểm còn lại sẽ có cách tính tương tự, giả sử điểm 1:1 (màu xanh lá như trong hình) sẽ có cách tính là trung bình cộng màu của pixel tại vị trí 1:1, 0:0, 0:1, 0:2, 1:0, 1:2, 2:0, 2:1, 2:2 và chia cho 9 (1 điểm gốc và 8 điểm xung quanh).

Và cứ thế, dùng phương pháp này cho 7 pixel còn lại ta được kết quả như sau.

So sánh lại hình đầu và kết quả cuối sau khi áp dụng giải thuật trên.

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

Ý tưởng là ta sẽ vét cạn từng pixel trên ảnh, và lấy màu của từng điểm rồi tính trung bình cộng của số điểm xung quanh có thể tính được.

"Có thể tính được" ở đây giả sử ta đang tính pixel ở vị trí 0:0, vậy chỉ có thể tính được ở vị trí xoay quanh nó là 0:1, 1:1, 1:0 (ta phải loại trừ các trường hợp như -1:0, -1:1, 0:-1, 1:-1 và các trường hợp nằm ở biên lẫn các góc khác.

void blur(unsigned char* & img, int width, int height, int bpp)
{
	unsigned char* imgTemp = img;
	img = new unsigned char[width * height * bpp / 8];

	int step = bpp / 8;
	
	unsigned short colorTemp[3] = {0};

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			int count = 1;

			colorTemp[0] = imgTemp[(i * width + j)*step + 0];
			colorTemp[1] = imgTemp[(i * width + j)*step + 1];
			colorTemp[2] = imgTemp[(i * width + j)*step + 2];

			// x o o
			// o + o
			// o o o
			if (i - 1 >= 0 && j - 1 >= 0)
			{
				count++;
				colorTemp[0] += imgTemp[((i - 1) * width + j - 1)*step + 0];
				colorTemp[1] += imgTemp[((i - 1) * width + j - 1)*step + 1];
				colorTemp[2] += imgTemp[((i - 1) * width + j - 1)*step + 2];
			}

			// o x o
			// o + o
			// o o o
			if (i - 1 >= 0)
			{
				count++;
				colorTemp[0] += imgTemp[((i - 1) * width + j)*step + 0];
				colorTemp[1] += imgTemp[((i - 1) * width + j)*step + 1];
				colorTemp[2] += imgTemp[((i - 1) * width + j)*step + 2];
			}

			// o o x
			// o + o
			// o o o			
			if (i - 1 >= 0 && j + 1 < width)
			{
				count++;
				colorTemp[0] += imgTemp[((i - 1) * width + j + 1)*step + 0];
				colorTemp[1] += imgTemp[((i - 1) * width + j + 1)*step + 1];
				colorTemp[2] += imgTemp[((i - 1) * width + j + 1)*step + 2];
			}

			// o o o
			// o + x
			// o o o
			if (j + 1 < width)
			{
				count++;
				colorTemp[0] += imgTemp[(i * width + j + 1)*step + 0];
				colorTemp[1] += imgTemp[(i * width + j + 1)*step + 1];
				colorTemp[2] += imgTemp[(i * width + j + 1)*step + 2];
			}

			// o o o
			// o + o
			// o o x
			if (i + 1 < height && j + 1 < width)
			{
				count++;
				colorTemp[0] += imgTemp[((i + 1) * width + j + 1)*step + 0];
				colorTemp[1] += imgTemp[((i + 1) * width + j + 1)*step + 1];
				colorTemp[2] += imgTemp[((i + 1) * width + j + 1)*step + 2];
			}

			// o o o
			// o + o
			// o x o
			if (i + 1 < height)
			{
				count++;
				colorTemp[0] += imgTemp[((i + 1) * width + j)*step + 0];
				colorTemp[1] += imgTemp[((i + 1) * width + j)*step + 1];
				colorTemp[2] += imgTemp[((i + 1) * width + j)*step + 2];
			}

			// o o o
			// o + o
			// x o o
			if (i + 1 < height && j - 1 >= 0)
			{
				count++;
				colorTemp[0] += imgTemp[((i + 1) * width + j - 1)*step + 0];
				colorTemp[1] += imgTemp[((i + 1) * width + j - 1)*step + 1];
				colorTemp[2] += imgTemp[((i + 1) * width + j - 1)*step + 2];
			}

			// o o o
			// x + o
			// o o o
			if (j - 1 >= 0)
			{
				count++;
				colorTemp[0] += imgTemp[(i * width + j - 1)*step + 0];
				colorTemp[1] += imgTemp[(i * width + j - 1)*step + 1];
				colorTemp[2] += imgTemp[(i * width + j - 1)*step + 2];
			}

			img[(i * width + j)*step + 0] = colorTemp[0] / count;
			img[(i * width + j)*step + 1] = colorTemp[1] / count;
			img[(i * width + j)*step + 2] = colorTemp[2] / count;
			(step == 4) && (img[(i * width + j)*step + 3] = imgTemp[(i * width + j)*step + 3]);
		}
	}

	delete []imgTemp;
}

Nâng cao

Hiện tại giải thuật chỉ lấy trung bình cộng các pixel gần nhất (bán kính 1 pixel), mở rộng ra để đáp ứng làm mờ ảnh với bán kính lớn hơn (độ mờ cao hơn) với mã bên dưới:

void BlurImage(int width, int height, int byte_per_pixel, byte* &data, int radius)
{
	if (radius == 0) return;

	byte* temp = data;

	int resolution = width * height;

	data = new byte[resolution * byte_per_pixel];

	int r0;
	int c0;

	int hA;
	int hB;
	int wA;
	int wB;
	
	int color[3];
	int count;

	unsigned int radius2 = radius * radius;

	for (int index0 = 0; index0 < resolution; index0++)
	{
		r0 = index0 / width;
		c0 = index0 % width;

		hA = r0 - radius >= 0 ? r0 - radius : 0;
		hB = r0 + radius <= height ? r0 + radius : height;

		wA = c0 - radius >= 0 ? c0 - radius : 0;
		wB = c0 + radius <= width ? c0 + radius : width;

		color[0] = color[1] = color[2] = count = 0;

		unsigned int distance2;
		int index;

		for (int r = hA; r < hB; r++)
		{
			for (int c = wA; c < wB; c++)
			{
				distance2 = (c - c0) *  (c - c0) + (r - r0) * (r - r0);

				if (c0 >= width / 2)
				{
					if (distance2 < radius2)
					{
						index = (r * width + c) * byte_per_pixel;

						color[0] += temp[index + 0];
						color[1] += temp[index + 1];
						color[2] += temp[index + 2];

						count++;
					}
				}
				else
				{
					index = (r * width + c) * byte_per_pixel;

					color[0] += temp[index + 0];
					color[1] += temp[index + 1];
					color[2] += temp[index + 2];

					count++;
				}
			}
		}

		data[index0 * byte_per_pixel + 0] = color[0] / count;
		data[index0 * byte_per_pixel + 1] = color[1] / count;
		data[index0 * byte_per_pixel + 2] = color[2] / count;

		if (byte_per_pixel == 4)
			data[index0 * byte_per_pixel + 3] = temp[index0 * byte_per_pixel + 3];
	}

	delete temp;
}
IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024