Nội dung bài viết
Đăng ký học lập trình C++
Tại STDIO bạn được dạy nền tảng lập trình tốt nhất.
Đăng ký học
La Kiến Vinh Óc quan sát và phân tích vấn đề sẽ giúp cho tư duy chúng ta tốt hơn. Bài viết này chủ yếu phân tích và hiện thực một hiệu ứng làm blur ảnh đơn giản, từ đó cung cấp ý tưởng cho các bạn xử lý ảnh với các mục tiêu khác nhau và các hiệu ứng khác nhau.

Giới thiệu

Nhằm giúp cho các bạn đang muốn hiểu sâu hơn và nắm được nguyên lý của việc xử lý ảnh, thay vì dùng các công cụ mạnh có sẵn, tôi chủ yếu phân tích 1 hiệu ứng và hướng dẫn các bạn hiểu được nguyên lý cơ bản nhất của xử lý ảnh.

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:

Tiền đề bài viết

Hôm nay là ngày cuối cùng tôi dạy cho 1 nhóm học trò của mình, mặc dù tôi đã giúp được các bạn chinh phục được con trỏ nhưng tôi đáng tiếc rằng không có được nhiều thời gian hơn để giúp các bạn vì thời lượng đào tạo của trường có giới hạn, nhằm để cho các bạn hiểu rằng, tôi vẫn không bỏ rơi các bạn và muốn các bạn phải vượt qua được sự lười biếng và vượt qua được sự hờ hững của bản thân khi chinh phục 1 điều mới mẻ, trước khi về tôi đã nói nói với các bạn là hãy tự làm bài này, tôi viết bài này và tôi muốn nhắn nhủ rằng "tôi không đủ sức phán xét các bạn, nhưng chính bạn trong tương lai sẽ phán xét bạn lúc này".

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

Dành cho các học trò của tôi, các bạn đọc khác có thể đọc tham khảo hoặc bình luận nếu chưa hiểu rõ, yêu cầu phải nắm được 1 ít kiến thức về pixel RGB888, RGBA8888 và chút ít về con trỏ và cấp phát động, tôi dùng C++ để hiện thực ý tưởng này, các ngôn ngữ khác sẽ tương tự.

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.

ss_1
HÌNH 1 - Rõ nét

ss_2
HÌNH 2 - Mờ hơn HÌNH 1

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

ss_3

Điều mong đợi là ta sẽ làm mờ ảnh trên. Công việc của ta là phải 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 và cách đơn giản ta có thể nghĩ ra đó là dùng 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).

ss_4

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).

ss_5

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.

ss_6

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.

ss_7

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 blurMore(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 này rất đơn giản, chỉ tính các điểm lân cận, mở rộng ra ta có thể thêm 1 thông số là weight (trọng số) để tính với 1 nhóm điểm với giới hạn lớn hơn, sẽ tạo ra ảnh có độ nhòe cao hơn nữa.

THẢO LUẬN
ĐÓNG