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:
- 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.
- Phép Tích Chập Trong Xử Lý Ảnh (Convolution).
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.
* Ả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; }