Search…

Cách Thức Lưu Trữ Hình Ảnh bằng Cấu Trúc Mat trong OpenCV

08/11/202011 min read
Giới thiệu cách lưu trữ hình ảnh bằng cấu trúc dữ liệu Mat trong OpenCV và sử dụng nó linh hoạt.

Cấu trúc dữ liệu Mat

Hình bên dưới mô phỏng cách mà con người và máy tính cùng "nhìn" 1 hình ảnh.

  • Cách con người nhìn chính là hình ảnh thực của nó.
  • Máy tính chỉ hiểu hình ảnh ở những giá trị có thể quy đổi ra số.

Hình ảnh là 1 hình chữ nhật nên thông thường được hình dung dưới 1 ma trận, chính vì vậy OpenCV xây dựng class Mat để lưu trữ thông tin hình ảnh, nhờ vậy mà dễ thao tác và tối ưu hơn khi lập trình.

Người nhìn thấy (trái) - Máy nhìn thấy (phải)

Dưới đây là hình mô phỏng ma trận lưu hình ảnh:

  Column 0 Column 1 Column ... Column m
Row 0 0, 0 0, 1 ..., ... 0, m
Row 1 1, 0 1, 1 ..., ... 1, m
Row ... ..., 0 ..., 1 ..., ... ..., m
Row n n, 0 n, 1 n, ... n, m

Ma trận sẽ gồm các dòng và các cột, hình dung như chiều cao của hình ảnh sẽ bằng đúng số dòng 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.

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, tại phần tử thứ [row 0, column 0] là giá trị màu sắc của 1 pixel của hình ảnh. 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.

Đa số mỗi kênh màu sẽ được biểu diễn bằng 8-bit 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].

  Column 0 Column 1 Column ... Column m
Row 0  0, 0   0, 0   0, 0   0, 1   0, 1   0, 1   ..., ...   ..., ...   ..., ...   0, m   0, m   0, m 
Row 1  1, 0   1, 0   1, 0   1, 1   1, 1   1, 1   ..., ...   ..., ...   ..., ...   1, m   1, m   1, m 
Row ...  ..., 0   ..., 0   ..., 0   ..., 1   ..., 1   ..., 1   ..., ...   ..., ...   ..., ...   ..., m   ..., m   ..., m 
Row n  n, 0   n, 0   n, 0   n, 1   n, 1   n, 1   n, ...   n, ...   n, ...   n, m   n, m   n, m 

Sử dụng Mat trong OpenCV

Constructor và ý nghĩa của chúng

Lớp Mat trong OpenCV nằm trong module core của bộ thư viện OpenCV và nằm trong namespace cv của bộ thư viện. Nếu không khai báo sử dụng namespace cv ở đầu chương trình thì trong chương trình sử dụng bắt buộc có tiền tố cv.

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

Mat(int rows, int cols, int type);
  • rows: số dòng của ma trận hay nói cách khác là chiều cao của hình ảnh.
  • cols: số cột của ma trận hay nói cách khác là chiều rộng của hình ảnh.
  • type: có cấu trúc như 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ụ:
      • CV_8UC1: mỗi pixel có 1 channel dùng 8-bit không dấu để biểu diễn.
      • CV_8UC3: mỗi pixel có 3 channel và ứng mới mỗi channel sẽ dùng 8-bit không dấu để biểu diễn (RGB, BRG, ...).
      • CV_8UC4: mỗi pixel có 4 channel và ứng mới mỗi channel sẽ dùng 8-bit không dấu để biểu diễn (ARGB, BRGA).
Mat(Size size, int type);

Tương tự như constructor trên nhưng thay vì truyền vào rowscols thì truyền vào size với với format Size(cols, rows)

Mat(int rows, int cols, int type, const Scalar& s);

Giống như contructor thứ nhất với đối số thứ ba là 1 option, giá trị s có ý nghĩa là khởi tạo giá trị cho các phần tử trong Mat.

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

mat.clone();

Phương thức trả về đối tượng Mat có dữ liệu giống với Mat

imageRed.copyTo(OutputArray m);

Giống như phương thức trên nhưng phương thức này phải truyền vào tham số là 1 OutputArray

Ví dụ:

Mat F = A.clone();
Mat G;
A.copyTo(G);

3 phương thức khởi tạo các giá trị đặc biệt - zeros, ones, eyes

Mat::zeros(int rows, int cols, int type);

Phương thức tạo 1 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ề 1 đối tượng Mat.

Mat::zeros(Size size, int type);

Giống phương thức trên thay vì truyền vào rowscols thì truyền vào size có format là Size(cols, rows).

Ví dụ 1:

Mat zero_6_6 = Mat::zeros(6, 6, CV_8UC1);
cout << zero_6_6 << endl;

Kết quả:

[ 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 ]

Ví dụ 2:

Mat zero_6_18 = Mat::zeros(6, 6, CV_8UC3);
cout << zero_6_18 << endl;

Kết quả:

[ 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 ]

Trong 2 trường hợp trên truyền vào rowscols bằng nhau nhưng khác type thì kết quả như sau:

  • type = CV_8UC1: thì matrix sẽ là 6 rows và 6 cols.
  • type = CV_8UC3: thì matrix sẽ là 6 rows và 18 cols, 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 cột là 18 nhưng thực tế chiều rộng của hình ảnh vẫn là 6.
Mat::ones(int rows,int cols, int type);

Phương thức này trả về 1 Mat có các phần tử mang giá trị 1.

Ví dụ:

Mat one_10_10 = Mat::ones(10, 10, CV_8UC1);
cout << one_10_10 << endl;

Kết quả:

[ 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 1 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:

Mat eye_10_10 = Mat::eye(10, 10, CV_8UC1);
    cout << eye_10_10 << endl;

Kết quả:

[ 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 ]

Ví dụ 2:

Mat eye_10_15 = Mat::eye(10, 15, CV_8UC1);
cout << eye_10_15 << endl;

Kết quả:

[ 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 ]

Khi sử dụng phương thức eye 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

//
//  main.cpp
//  OpenCVTest
//
//  Created by NguyenNghia on 11/11/16.
//  Copyright © 2016 nguyennghia. All rights reserved.
//

#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ả:

Hoặc random các phần tử của matrix trong khoảng 0, 255

Mat R = Mat(480, 320, CV_8UC3);
rand(R, Scalar::all(0), Scalar::all(255));

Kết quả khi show R:

namedWindow("Random Image", WINDOW_AUTOSIZE);
imshow("Random Image", R);

Có thể chạy thử đoạn mã dưới đây để hiểu hơn về Mat trong thư viện OpenCV

/*  For description look into the help() function. */
#include <iostream>
#include "opencv2/core/core.hpp"

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_(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 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 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;
}

Ngoài việc sử dụng cấu trúc dữ liệu này để lưu trữ dữ liệu 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.

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024