20/4/13

Hiển thị ảnh xử lý bằng OpenCV lên Picturebox của Visual Studio MFC


Hiển thị ảnh xử lý bằng OpenCV lên Picturebox của Visual Studio MFC

Việc sử dụng OpenCV để lập trình cho xử lý ảnh và dùng chung cùng dao diện của MFC không phải là một chuyện đơn giản và thường có nhiều vướng mắc đối với những ai mới bước đầu làm quen. Trong bài viết này tôi sẽ hướng dẫn các newbi làm một bài thực hành đơn giản để gải tỏa các vướng mắc và làm quen dần với thư viện này. Bài thực hành đơn giản là load một ảnh rồi hiển thị ảnh đó lên picture box của MFC, sau đó dùng các hàm của OpenCV chuyển ảnh này thành ảnh xám và hiển thị lên một picture box khác. Giao diện chương trình như sau:

OK, bắt đầu nhé!
Đầu tiên ta khởi tạo một project sử dụng MFC, các bạn vào  File->New->Project. Chọn loại ứng dụng là MFC Application, đặt tên cho Project rồi click OK.
Môt hộp thoại hiện ra, các bạn chọn Next, đến hộp thoại tiếp theo các bạn chọn vào  như hình sau:
Chọn Next một vài lần nữa cho tới khi cửa sổ cuối cùng như sau hiện ra:
 Khi đó trong mục Generate Classes hãy chọn lớp thứ hai (có dạng ...Dlg) để chương trình khởi động tự động lớp Dialog.
 Nhấn Finish để kết thúc, như vậy ta đã có một project bằng MFC dựa trên giao diện Dialog. Để config cho Project có thể chạy được với OpenCV các bạn hãy vào Project Properties hoặc nhấn Alt + F7 để tuy chỉnh. Cách tùy chỉnh cũng giống như đối với Project dạng console mà tôi đã giới thiệu trước đó, các bạn có thểtham khảo thêm ở đây.

Ok, bây giờ ta hãy thiết kế một giao diện như hình đầu tiên. Giao diện gồm một button có nhãn Load Image, một button Convert to Scale Image. 2 Picture Control, và các Group box. Để lôi được các item này ra các bạn hãy vào ToolBox và kéo thả bình thường.

Để thay đổi các thuộc tính của các Item này hãy click chuột phải vào Item cần tùy chỉnh, chọn Properties (ví dụ như thay đổi tên hiển thị thì tìm mục caption mà đổi ...). Riêng đối với 2 Picture Control để hiển thị ảnh được và giúp lập trình đơn giản thì ta cần làm theo các một số việc sau:
- Thứ nhất, mặc định của Picture Control là dạng Frame. Để hiển thị ảnh Bitmap ta cần chỉnh thành Bitmap, để chỉnh sữa, click chuột phải chọn Properties, trong bảng Properties, chọn mục Type, sau đó đổi Frame thành Bitmap.
- Thứ hai, tất cả các Item trong MFC đều được định sẵn một ID. Để tiện lợi cho việc thay lập trình ta sẽ truy xuất các ID này bằng các biến. Như vậy ta cần gắn cho các Picture Control một biến truy cập. Để gắn được biến cho Picture Control, trược hết phải đổi thuộc tính STATIC của chúng. Để đổi thuộc tính này, trong mục Properties của Picture Control, chon ID, chỉnh sữa IDC_STATIC thành bất kì cái gì bạn muốn, thông thường nên chỉnh thành IDC_STATIC1 chẳn hạn. Picture Control thứ hai là IDC_STATIC2. Sau khi đổi được thuộc tính STATIC, hãy click chuột phải vào biểu tượng Picture Control và chọn Add Variable... Một bảng hiện ra, hãy đặt tên lần lượt cho các Picture Control là pic1 và pic2 như sau:


Và bây giờ ta có thể truy xuất tới các Picture Control bày bằng các biến pic1, pic2. Để đặt một ảnh bitmap lên control này ta có thể dùng lệnhpic1.SetBitmap(bitmap);  
Vấn đề lớn nhất ở đây là hàm SetBitmap của MFC chỉ nhận duy nhất tham số đầu vào là một ảnh Bitmap (Trong khi có nhất nhiều định dạng ảnh khác nữa: Jpeg, png, ...) và với định dạng IplImage của OpenCV thì làm thế nào?
Có hai giải pháp được đưa ra (có thể có nhiều nhưng tui mới biết có hai ... :D). Thứ nhất, chuyển đổi định dạng IplImage của OpenCV ra thành định dạng bitmap bằng cách dùng các hàm chuyển đồi. Thứ hai, lưu ảnh xử lý bằng OpenCV thành Bitmap sau đó load ảnh bằng hàm LoadImage của MFC rồi hiển thị ảnh. Tôi sẽ giới thiệu cả hai cách.

♥ Chuyển đổi IplImage của OpenCV sang định dạng Bitmap
Các thông tin về định dạng của một bức ảnh được lưu ở header của ảnh. Do đó để chuyển đổi định dạng của ảnh ta chỉ cần can thiệp vào header của nó. Đoạn code sau sẽ làm việc đó.

inline HBITMAP IplImage2DIB(const IplImage *Image)    // Convert IPl Image to Bitmap
{
  int bpp = Image->nChannels * 8;
        assert(Image->width >= 0 && Image->height >= 0 &&(bpp == 8 || bpp == 24 || bpp == 32));
        CvMat dst;
        void* dst_ptr = 0;
        HBITMAP hbmp = NULL;
        unsigned char buffer[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
        BITMAPINFO* bmi = (BITMAPINFO*)buffer;
        BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);
        
        ZeroMemory(bmih, sizeof(BITMAPINFOHEADER));
        bmih->biSize = sizeof(BITMAPINFOHEADER);
        bmih->biWidth = Image->width;
        bmih->biHeight = Image->origin ? abs(Image->height) :
        -abs(Image->height);
        bmih->biPlanes = 1;
        bmih->biBitCount = bpp;
        bmih->biCompression = BI_RGB;
        
        if (bpp == 8) {
                RGBQUAD* palette = bmi->bmiColors;
                int i;
                for (i = 0; i < 256; i++) {
                        palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue
                                = (BYTE)i;
                        palette[i].rgbReserved = 0;
                }
        }
        
        hbmp = CreateDIBSection(NULL, bmi, DIB_RGB_COLORS, &dst_ptr, 0, 0);
        cvInitMatHeader(&dst, Image->height, Image->width, CV_8UC3,
                dst_ptr, (Image->width * Image->nChannels + 3) & -4);
        cvConvertImage(Image, &dst, Image->origin ? CV_CVTIMG_FLIP : 0);
        
        return hbmp;      
}


♥ Bằng cách lưu ảnh lại dưới dạng bitmap, sau đó load và hiển thị ảnh:
Sử dụng hàm của OpenCV để lưu ảnh: cvSaveImage(ten_anh.bmp, du_lieu_anh); sau đó dùng hàm LoadImage(); của MFC để load ảnh bitmap và hiển thị lên Picture box.

Quay lại Project, hãy copy đoạn code trên pass vào file ten_projectDlg.cpp ngay dưới phần include các header (Nhớ là include opencv.hpp vào đã nhé.). sau đó hãy ta xử lý sự kiện click chuột ở các button Load Image và Convert to Scale Image như sau:
Double click vào button Load Image và thêm vào đoạn code như hình sau:

 Ở button Convert to Gray Image ta viết đoạn code sau:
Chú ý là các ảnh src, gray, resize và file_name có thể được khai báo trong header ten_projectDlg.h
Để kích thước của ảnh vừa khít với khung của dao diện thiết kế, ta có thể chỉnh tham số của ảnh resize trong hàm cvCreateImage(). (trong bài là 400x300 pixel). Một số trường hợp báo lỗi do khai báo file_name là kiểu CString còn tham sốcvLoadImage lại là const char*. Để khắc phục lỗi này hãy tắt chế độ unicode của chương trình đi bằng cách vào Project Properties (Alt + F7) -> General. Ở trang bên của General, mục Character Set chỉnh Use Unicode Character Set thành Use Multi-Byte Character Set.
Sau đây là kết quả chạy chương trình:


PS: Một số khái niệm nhỏ hoặc các bước đơn giản trung gian khác nếu các bạn chưa rõ có thể tìm hiểu thêm, trong bài có đôi lúc gọi Picture Control là Picture Box ...

Toàn bộ project các bạn có thể download tại đây

                                                                      Chúc các bạn học tập tốt!

Bài đăng phổ biến