Chúng ta đã tìm hiểu 3 bài toán cơ bản thường gặp và hay dùng đó là Cộng 2 vector, Nhân 2 ma trận và Tính tổng các phần tử của mảng. Chúng ta cũng đã biết viết chương trình bằng CUDA và cách để song song hóa 1 thuật toán hay 1 chương trình để chạy trên GPU.
Hôm nay, chúng ta sẽ cùng nhau tìm hiểu phép tính tích chập (Convolution) để áp dụng vào bài toán làm mờ ảnh. Phép tích chập được sử dụng rất nhiều trong AI, Machine Learning, Deep Learning…
Tích chập (Convolution)
Tích chập (Convolution) là 1 phép toán thực hiện đối với 2 hàm số f và g, kết quả cho ra 1 hàm số thứ 3. Nó được ứng dụng trong xác suất, thống kê, thị giác máy tính (computer vision), xử lý ảnh, xử lý tín hiệu, kỹ thuật điện, học máy, và các phương trình vi phân.
Trong xử lý ảnh, được sử dụng chính yếu trong các phép toán trên ảnh như: đạo hàm ảnh, làm mờ, làm trơn ảnh, trích xuất biên cạnh trong ảnh.
Ở phần sau chúng ta sẽ áp dụng tính tích chập vào bài toán làm mờ ảnh.
Làm mờ ảnh (Blur)
Trong xử lý ảnh, phép làm mờ ảnh được dùng rất nhiều và có nhiều vai trò quan trọng. Hiệu ứng làm mờ áp dụng trong các trường hợp:
- Giảm nhiễu (noise) trong ảnh
- Làm trơn ảnh (smooth)
Tính toán xử lý cho phép biến đổi làm mờ ảnh chính là dùng toán tử convolution để áp cửa sổ hay bộ lọc lên ảnh gốc.
Việc làm mờ ảnh hình dung thực hiện bằng việc dịch chuyển ma trận kernel lần lượt qua tất cả các điểm ảnh (pixel) trong ảnh, bắt đầu từ góc bên trái trên của ảnh. Và đặt anchor point tương ứng tại điểm ảnh đang xét. Ở mỗi lần dịch chuyển, thực hiện tính toán kết quả mới cho điểm ảnh đang xét bằng công thức tích chập.
Kích thước cửa sổ của các bộ lọc (Filter) làm thường là 3, 5, 7, 9, … Chính vì kích thước lẻ nên ta sẽ chỉ có 1 pixel ở trung tâm kernel.
Việc chọn kích thước bộ lọc thường dựa vào kích thước ảnh đầu vào và kinh nghiệm. Kernel thường được thiết kế hình vuông (tức width = height).
Bây giờ, chúng ta cùng thực hiện code bài toán làm mờ ảnh để chạy trên CPU và GPU nhé.
Ta có thông số về chiều cao (height) và chiều rộng (width) của ảnh. Giá trị RBG trên mỗi pixel mình xem như đã có và được lưu trong inPixels.
1 2 3 4 5 6 7 8 9 10 |
int width, height; uchar3 * inPixels; uchar3 * outPixels; // Read input image file pixels = (uchar3 *)malloc(width * height * sizeof(uchar3)); for (int i = 0; i < width * height; i++) pixels[i].x = 1; pixels[i].y = 1; pixels[i].z) = 1; |
Ta chọn bộ lọc kích thước 9×9
1 2 3 4 5 6 7 8 9 10 |
// Set up a simple filter with blurring effect int filterWidth = 9; float * filter = (float *)malloc(filterWidth * filterWidth * sizeof(float)); for (int filterR = 0; filterR < filterWidth; filterR++) { for (int filterC = 0; filterC < filterWidth; filterC++) { filter[filterR * filterWidth + filterC] = 1. / (filterWidth * filterWidth); } } |
Hàm làm mờ ảnh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void blurImg(uchar3 * inPixels, int width, int height, float * filter, int filterWidth, uchar3 * outPixels) { for (int outPixelsR = 0; outPixelsR < height; outPixelsR++) { for (int outPixelsC = 0; outPixelsC < width; outPixelsC++) { float3 outPixel = make_float3(0, 0, 0); for (int filterR = 0; filterR < filterWidth; filterR++) { for (int filterC = 0; filterC < filterWidth; filterC++) { float filterVal = filter[filterR * filterWidth + filterC]; int inPixelsR = (outPixelsR - filterWidth/2) + filterR; int inPixelsC = (outPixelsC - filterWidth/2) + filterC; inPixelsR = min(height - 1, max(0, inPixelsR)); inPixelsC = min(width - 1, max(0, inPixelsC)); uchar3 inPixel = inPixels[inPixelsR * width + inPixelsC]; outPixel.x += filterVal * inPixel.x; outPixel.y += filterVal * inPixel.y; outPixel.z += filterVal * inPixel.z; } } outPixels[outPixelsR * width + outPixelsC] = make_uchar3(outPixel.x, outPixel.y, outPixel.z); } } } |
Bây giờ chúng ta chuyển hàm làm mờ ảnh sang CUDA để chạy song song trên GPU.
Mình chạy thử với block size 32×32
1 2 3 |
dim3 blockSize(32, 32); dim3 gridSize((width - 1) / blockSize.x + 1, (height - 1) / blockSize.y + 1); blurImgKernel<<<gridSize, blockSize>>>(inPixelsDevice, width, height, filterDevice, filterWidth, outPixelsDevice); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
__global__ void blurImgKernel(uchar3 * inPixels, int width, int height, float * filter, int filterWidth, uchar3 * outPixels) { int px = blockIdx.x * blockDim.x + threadIdx.x; int py = blockIdx.y * blockDim.y + threadIdx.y; if (px >= width || py >= height) { return; } float3 outPixel = make_float3(0, 0, 0); for (int filterR = 0; filterR < filterWidth; filterR++) { for (int filterC = 0; filterC < filterWidth; filterC++) { float filterVal = filter[filterR * filterWidth + filterC]; int inPixelsR = (py - filterWidth/2) + filterR; int inPixelsC = (px - filterWidth/2) + filterC; inPixelsR = min(height - 1, max(0, inPixelsR)); inPixelsC = min(width - 1, max(0, inPixelsC)); uchar3 inPixel = inPixels[inPixelsR * width + inPixelsC]; outPixel.x += filterVal * inPixel.x; outPixel.y += filterVal * inPixel.y; outPixel.z += filterVal * inPixel.z; } } outPixels[py * width + px] = make_uchar3(outPixel.x, outPixel.y, outPixel.z); } |
Các bạn chạy thử xem sao nhé!
Leave a Reply