Sep 5, 2013

Load ảnh từ OpenCV

Chào mừng năm học mới, thắng lợi mới nên mình sẽ viết một loạt series về opencv và một số kiến thức liên quan đến thị giác máy tính. Một phần để phục vụ việc học tập( ngâm cứu) của bản thân, một phần chia sẻ kiến thức với bạn nào có đam mê và muốn thử với lĩnh vực này.

Chủ đề của hôm nay. Mở đầu năm học bằng 1 công việc rất đơn giản: load ảnh dùng opencv.
Ngôn ngữ:C++, sẽ bàn một chút đến C và Python.
Để load 1 ảnh đã tồn tại trong bộ nhớ ngoài, ta dùng API imread.
Tham số của API này:
  • filename (const char*): đường dẫn đến file cần load.
  • flags (int): các tùy chọn khi load ảnh. Một số tham số hay ho:
    • &gt 0: Trả lại ảnh có 3 kênh màu. Kênh $ \alpha $ sẽ bị lược bỏ.
    • = 0: Trả về ảnh mức xám.
    • &lt 0: Trả về ảnh nguyên gốc.
imread sẽ trả lại là một đối tượng kiểu Mat. Nếu không thành công, kết quả trả về sẽ là một ma trận rỗng, tức Mat::data = NULL. Ta có thể dùng kết quả này để check xem file thực sự đã được load hay chưa. Bởi vì imread không có cơ chế exeptions nên khi có lỗi xảy ra, ta phải đợi đến các hàm xử lý tiếp theo (có exceptions hoặc assert) mới biết được. Điều này rất là bất lợi khi debug: ta cứ nghĩ ảnh đã được load vào, nhưng thực sự là chưa.
Sau khi load xong, công việc tiếp theo sẽ là show tấm ảnh đó ra màn hình. OpenCV hỗ trợ tốt tác vụ này với API: imshow. Tham số lần lượt của API này như sau:
  1. windows name (string): Tên của cửa sổ màn hình.
  2. image (Mat): biến lưu trữ ảnh đã được load
Đoạn mã ví dụ:

1
2
3
4
5
6
7
8
9
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
    Mat img = imread("hello.png", 1);
    imshow("show image", img);
    waitKey(0);
    return 0;
}

Hàm waitKey có một chức năng nho nhỏ. Nó sẽ đợi người dùng nhấn một phím bất kỳ rồi mới thực hiện công việc tiếp theo. Tham số đầu vào là 0, mặc định là vậy. Lớn hơn 0 là số milisecond mà waitKey sẽ delay.
Tất cả các api vừa kể trên là của C++, OpenCV hỗ trợ cả ngôn ngữ C. Để tránh nhầm lẫn đáng tiếc( vì C/C++ là hai anh em rất là gần gũi nhau), nên các API kể trên đều nằm trong namespace cv. Còn các cấu trúc khác thuộc C sẽ có tiền tố CV đứng trước.
Vậy ứng dụng của đoạn code nho nhỏ này là gì? Một điều hay ho của các API bên OpenCV là nó cross-platform. Rinh qua bên Linux chạy ngon, và để Windows cũng tốt chán. Vì cái sự cross-platform đó, và vì đây là thư viện tập trung vào xử lý ảnh, nên số các API hỗ trợ user interface rất ít. Nhưng, nhưng ít ra, nó cũng đủ cho ta chế 1 tool graphic debug. :D
Ý tưởng:
  • Một cửa sổ Windows cho phép resize, đồng thời là 1 điểm breakpoint kiểm tra xem đoạn code của ta tới đó đã thỏa mãn chưa.
  • (Option): Click vào mỗi pixel sẽ trả về giá trị ở pixel đó.
Hướng giải quyết:
  • Việc tạo 1 của sổ cho phép resize ảnh không khó, việc tạo breakpoint đơn thuần là gọi hàm waitKey().
  • Việc đọc giá trị của pixel phụ thuộc rất lớn vào: số kênh của ảnh (1, 3), và kiểu lưu trữ của mỗi kênh đó (uchar, float, double, int)... Việc click để chọn vị trí pixel khá đơn giản. Tuy nhiên, hiện nay mình vẫn chưa có cách thỏa đáng nào (ngoại trừ việc if...else một đống các trường hợp và gọi từng hàm phù hợp.
Tạo một header file. ví dụ là debugtool.h có nội dung sau:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifndef __DEBUGTOOL
#define __DEBUGTOOL

#include <cassert>
#include <opencv2/opencv.hpp>
void __clickevent(int evnt, int x, int y, int flags, void* params);
#ifdef NDEBUG
#define debug_img(img) 
#else
#define debug_img(img) {\
 cv::namedWindow("Debug:"#img, CV_WINDOW_NORMAL | CV_WINDOW_FREERATIO);\
 cv::setMouseCallback("Debug:"#img, __clickevent,(void*) &img);\
 cv::imshow("Debug:"#img, img);\
    cv::waitKey(0);\
 cv::destroyWindow("Debug:"#img);\
 }
#endif

#endif
Sau đó tạo file cpp tên debugtool.cpp có nội dung:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include "debugtool.h"

void __clickevent(int event, int x, int y, int flags, void* params)
{
    if (event != CV_EVENT_LBUTTONUP) return;
    IplImage *iplImg = new IplImage(*(cv::Mat*) params);   
    assert(iplImg->imageData != NULL);
    std::cout << "(" << x << "," << y << "): " 
                << cv::Scalar(cvGet2D(iplImg ,x, y)) << std::endl;
    std::cout.flush();
}

Xong, giờ có thể dùng debug_img bất cứ đâu như 1 breakpoint kiểm tra 1 ảnh. Click vào pixe để xem các giá trị. :)