Trong những bài viết trước mình đã hướng dẫn các bạn cơ bản về thư viện OpenCV. Trong các bài viết đó chúng ta có sử dụng cấu trúc dữ liệu Mat để lưu trữ data của hình ảnh. Và để hiểu rõ hơn về cấu trúc dữ liệu này sẽ hướng dẫn cho các bạn cách mà OpenCV lưu trữ data của hình ảnh trong cấu trúc dữ liệu này như thế nào cũng như cách sử dụng nó linh hoạt và dễ dàng nhất.
Giới thiệu
Trong những bài viết trước mình đã hướng dẫn các bạn cơ bản về thư viện OpenCV. Trong các bài viết đó chúng ta có sử dụng cấu trúc dữ liệu Mat để lưu trữ data của hình ảnh. Và để hiểu rõ hơn về cấu trúc dữ liệu này sẽ hướng dẫn cho các bạn cách mà OpenCV lưu trữ data của hình ảnh trong cấu trúc dữ liệu này như thế nào cũng như cách sử dụng nó linh hoạt và dễ dàng nhất.
Cấu trúc dữ liệu Mat
Chúng ta hãy nhìn qua hình dưới đây để thấy cách mà con người và máy tính cùng “nhìn” một hình ảnh. Cách mà con người nhìn chính là hình ảnh thực của nó. Phía máy tính chỉ hiểu hình ảnh ở những con số như hình ở dưới. Vì hình ảnh là một hình chữ nhật nên thông thường sẽ được hình dung dưới một Matrix chính vì vậy mà OpenCV xây dựng class Mat để lưu trữ thông tin hình ảnh cho gần gũi với người sử dụng.
Và dưới đây là hình mô phỏng ma trận lưu hình ảnh
Ma trận sẽ gồm các dòng và các cột. Hình dùng như chiều cao của hình ảnh sẽ bằng đúng số rows của matrix và chiều rộng của hình ảnh sẽ bằng đúng số cột của matrix. Và phần tử [row 0, colum 0] chính là đại diện cho 1 pixel của hình ảnh.
Và dưới đây là hình ảnh thực tế hơn về cách lưu hình ảnh ứng với bảng màu BGR. Chúng ta thấy tại phần tử thứ [row 0, column 0] là giá trị màu sắc của một pixel của hình ảnh. Và nó được lưu với không gian màu BGR nên mỗi pixel sẽ có 3 kênh màu kế tiếp nhau là B Blue, G Green, R Red. Và đa số mỗi kênh màu sẽ được biểu diễn bằng 8bit unsinged char (uint8_t). Tương tự với các phần tử khác trong ma trận cũng có cấu trúc lưu trữ tương đương với phần tử [row 0, column 0].
Sử dụng Mat trong OpenCV
Constructor và ý nghĩa của chúng
Lớp Mat trong OpenCV năm trong modules core của bộ thư viện OpenCV và năm trong namespace cv của bộ thư viện. Nếu chúng ta không khai báo sử dụng namespace cv ở đầu chương trình thì trong chương trình sử dụng phải bắt buộc có tiền tố cv đứng với đầu.
Ví dụ muốn sử dụng Mat phải khai báo là cv::Mat mat;
Các contructor thường sử dụng với Mat
1 |
Mat(int rows, int cols, int type); |
Tham số thứ nhất chính là số dòng của ma trận hay nói cách khác là chiều cao của hình ảnh.
Tham số thứ hai chính là số cột của trận hay cũng nói cách khác chính là chiều rộng của hình ảnh.
Tham số type thứ ba là tham số mà chúng ta nên quan tâm và hiểu rõ nó và type có cấu trức hư dưới đây
CV_[Số bit cho 1 channel][Kiểu có dấu, không dấu, số thực]C[Số channel]
Ví dụ như
CV_8UC1: Có nghĩa là mỗi có 1 channel dùng 8bit không dấu để biểu diễn
CV_8UC3: Có nghĩa là mội pixel (một điểm ảnh) sẽ có 3 channel và ứng mới mỗi channel sẽ dùng 8bit không dấu để biểu diễn. (RGB, BRG,….).
CV_8UC4: Có nghĩa là mội pixel (một điểm ảnh) sẽ có 4 channel và ứng mới mỗi channel sẽ dùng 8bit không dấu để biểu diễn. (ARGB, BRGA).
1 |
Mat(Size size, int type); |
Tương tự như constructor trên như thay vì truyền vào rows và cols thì chúng ta truyền vào size với với format Size(cols, rows)
1 |
Mat(int rows, int cols, int type, const Scalar& s); |
Giống như contructor thứ nhất với đối số thứ ba là một option. Giá trị s có ý nghĩa là sẽ khởi tạo giá trị cho các phần tử trong Mat.
1 |
Mat(Size size, int type, const Scalar& s); |
Constructor này tương tự như Constructor trên.
Các phương thức thường dùng với Mat
1 |
mat.clone(); |
Phương thức trả về đối tượng Mat có dữ liệu giống với mat
1 |
imageRed.copyTo(OutputArray m) |
Giống như phương thức trên nhưng phương thức này chúng ta phải truyền vào tham số là một OutputArray
Ví dụ
1 2 3 |
Mat F = A.clone(); Mat G; A.copyTo(G); |
Tiếp theo chúng ta sẽ đi qua 3 phương thức khởi tạo các giá trị đặc biệt là zeros, ones, eyes.
1 |
Mat::zeros(int rows, int cols, int type) |
Phương thức tạo một mat có các phần tử bằng giá trị 0, các tham số giống với tham số của Constructor. Phương thức này trả về một đối tượng Mat.
1 |
Mat::zeros(Size size, int type) |
Giống phương thức trên thay vì truyền vào rows và cols thì chúng ta truyền vào size có format là Size(cols, rows).
Ví dụ:
1 2 |
Mat zero_6_6 = Mat::zeros(6, 6, CV_8UC1); cout << zero_6_6 << endl; |
Kết quả xuất ra là:
1 2 3 4 5 6 |
[ 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0] |
Hay ví dụ
1 2 |
Mat zero_6_18 = Mat::zeros(6, 6, CV_8UC3); cout << zero_6_18 << endl; |
1 2 3 4 5 6 |
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
Các bạn thấy rằng chúng hai trường hợp trên chúng ta truyền vào rows và cols bằng nhau như khác type thì kết quả như sau:
type = CV_8UC1: Thì matrix sẽ là 6 rows và 6 column
type = CV_8UC3: thì matrix sẽ là 6 rows và 18 column, hiểu ở mức độ liên quan đên hình ảnh thì mỗi pixel sẻ sử dụng 3 kênh màu. Kích thước column và 18 nhưng thực tế chiều rộng của hình ảnh vẫn là 6.
1 |
Mat::ones(int rows,int cols, int type) |
Phương thức này sẽ trả về một Mat có các phần tử mang giá trị 1.
Ví dụ
1 2 |
Mat one_10_10 = Mat::ones(10, 10, CV_8UC1); cout << one_10_10 << endl; |
Kết quả
1 2 3 4 5 6 7 8 9 10 |
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] |
1 |
Mat::eye(int rows, int cols, int type) |
Phương thức static này khởi tạo một Mat có các phần tử trên đường chéo chính có giá trị 1 và các phần tử còn lại mang giá trị 0.
Ví dụ
1 2 |
Mat eye_10_10 = Mat::eye(10, 10, CV_8UC1); cout << eye_10_10 << endl; |
Kết quả
1 2 3 4 5 6 7 8 9 10 |
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 1, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 1, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 1, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 1, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 1, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 1, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 1, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 1, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] |
Một ví dụ khác
1 2 |
Mat eye_10_15 = Mat::eye(10, 15, CV_8UC1); cout << eye_10_15 << endl; |
Và kết quả
1 2 3 4 5 6 7 8 9 10 |
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0; 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] |
Chúng ta thấy rằng khi sử dụng phương thức eye này thì chỉ các phần thử có M[i, j] với i = j thì phần thử đó có giá trị bằng 1 còn lại các phần tử khác đều mang giá trị 0.
Tạo hình ảnh sử dụng Mat
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 33 34 35 36 37 38 |
// // main.cpp // OpenCVTest // // Created by NguyenNghia on 11/11/16. // Copyright © 2016 nguyennghia. All rights reserved. // #include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> using namespace std; using namespace cv; int main(int argc, const char * argv[]) { Mat imageRed(480,320, CV_8UC3, Scalar(0, 0, 255)); Mat imageGreen(480, 320, CV_8UC3, Scalar(0, 255, 0)); Mat imageBlue(480, 320, CV_8UC3, Scalar(255, 0, 0)); namedWindow("Image RED", WINDOW_AUTOSIZE); namedWindow("Image GREEN", WINDOW_AUTOSIZE); namedWindow("Image BLUE", WINDOW_AUTOSIZE); imshow("Image RED", imageRed); imshow("Image GREEN", imageGreen); imshow("Image BLUE", imageBlue); waitKey(); return 0; } |
Kết quả sau khi chạy chương trình
Hoặc random các phần tử trong matrix trong khoảng 0, 255
1 2 |
Mat R = Mat(480, 320, CV_8UC3); randu(R, Scalar::all(0), Scalar::all(255)); |
Và kết quả khi show R trên Windows
1 2 |
namedWindow("Random Image", WINDOW_AUTOSIZE); imshow("Random Image", R); |
Bạn có thể chạy thử đoạn mã dưới đây để hiểu hơn về Mat trong thư viện OpenCV
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
/* For description look into the help() function. */ #include "opencv2/core/core.hpp" #include <iostream> using namespace std; using namespace cv; static void help() { cout << "\n--------------------------------------------------------------------------" << endl << "This program shows how to create matrices(cv::Mat) in OpenCV and its serial" << " out capabilities" << endl << "That is, cv::Mat M(...); M.create and cout << M. " << endl << "Shows how output can be formated to OpenCV, python, numpy, csv and C styles." << endl << "Usage:" << endl << "./cvout_sample" << endl << "--------------------------------------------------------------------------" << endl << endl; } int main(int,char**) { help(); // create by using the constructor Mat M(2,2, CV_8UC3, Scalar(0,0,255)); cout << "M = " << endl << " " << M << endl << endl; // create by using the create function() M.create(4,4, CV_8UC(2)); cout << "M = "<< endl << " " << M << endl << endl; // create multidimensional matrices int sz[3] = {2,2,2}; Mat L(3,sz, CV_8UC(1), Scalar::all(0)); // Cannot print via operator << // Create using MATLAB style eye, ones or zero matrix Mat E = Mat::eye(4, 4, CV_64F); cout << "E = " << endl << " " << E << endl << endl; Mat O = Mat::ones(2, 2, CV_32F); cout << "O = " << endl << " " << O << endl << endl; Mat Z = Mat::zeros(3,3, CV_8UC1); cout << "Z = " << endl << " " << Z << endl << endl; // create a 3x3 double-precision identity matrix Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); cout << "C = " << endl << " " << C << endl << endl; Mat RowClone = C.row(1).clone(); cout << "RowClone = " << endl << " " << RowClone << endl << endl; // Fill a matrix with random values Mat R = Mat(3, 2, CV_8UC3); randu(R, Scalar::all(0), Scalar::all(255)); // Demonstrate the output formating options cout << "R (default) = " << endl << R << endl << endl; cout << "R (python) = " << endl << format(R,"python") << endl << endl; cout << "R (numpy) = " << endl << format(R,"numpy" ) << endl << endl; cout << "R (csv) = " << endl << format(R,"csv" ) << endl << endl; cout << "R (c) = " << endl << format(R,"C" ) << endl << endl; Point2f P(5, 1); cout << "Point (2D) = " << P << endl << endl; Point3f P3f(2, 6, 7); cout << "Point (3D) = " << P3f << endl << endl; vector<float> v; v.push_back( (float)CV_PI); v.push_back(2); v.push_back(3.01f); cout << "Vector of floats via Mat = " << Mat(v) << endl << endl; vector<Point2f> vPoints(20); for (size_t i = 0; i < vPoints.size(); ++i) vPoints[i] = Point2f((float)(i * 5), (float)(i % 7)); cout << "A vector of 2D Points = " << vPoints << endl << endl; return 0; } |
Kết luận
Trong bài viết này mình giới thiệu cho các bạn về cấu trúc dữ liệu lưu trữ hình ảnh Mat sử dụng trong OpenCV. Ngoài việc sử dụng cấu trúc dữ liệu này để lưu trữ data hình ảnh thì cấu trúc dữ liệu này được sử dụng khá nhiều trong việc tính toán và sử dụng các bộ lọc (filter) trong OpenCV mà chúng ta sẽ tìm hiểu ở bài viết sau.
Mình có 1 camera IDS với thư viện ueye.h, hình ảnh của camera được lưu trong vùng nhớ của nó dưới dạng sequence. Cho mình hỏi trong OPenCV có hàm nào để chuyển từ dạng đó về MAT để xử lí trong openCV được không?
Cảm ơn Bạn!!!
Không biết bài này có giúp gì được cho bạn không.
http://hasanaga.info/read-frame-from-ueye-camera-with-ueye-sdk-opencv/
Cho mình hỏi là mình muốn tạo video từ 1 bức ảnh thì sử dụng hàm nào vậy, ví dụ như các hiệu ứng trong video này https://www.youtube.com/watch?v=QBhZZr64B0U