Cấu trúc dữ liệu khởi thủy

Một phần của tài liệu Thuật toán phát hiện chuyển động (Trang 45)

OpenCV có nhiều kiểu dữ liệu khởi thủy không bắt nguồn từ quan điểm của ngôn ngữ C nhƣng lại là các cấu trúc đơn giản và đƣợc xem nhƣ là cơ bản trong việc xử lý đồ họa. Kiểu đơn giản nhất là CvPoint gồm 2 thành phần nguyên x và y. CvPoint cũng có 2 dạng khác là CvPoint2D32f và CvPoint3D32f để biểu diễn một điểm trong không gian 2 chiều và 3 chiều với tọa độ thực. CvSize cũng giống nhƣ CvPoint cũng gồm 2 thành phần width và height để mang thông tin về độ rộng và độ cao của đối tƣợng biểu diễn. CvRect là con của CvPoint và CvSize gồm 4 thành phần x, y, width và height.

Vì đƣợc xây dựng dựa trên nền tảng C nên hầu hết các cấu trúc dữ liệu sẽ có phƣơng thức cấu tử với tên giống nhƣ tên của cấu trúc nhƣng chỉ khác chữ cái đầu tiên không viết hoa. Các phƣơng thức này chỉ là các hàm “inline” để trả về cấu trúc thích hợp. Chúng ta có thể lấy một ví dụ đơn giản về việc tạo ra một hình chữ nhật trắng từ điểm (5,10) tới (20,30) chỉ bằng lời gọi đơn giản:

cvRectangle( myImg, cvPoint(5,10), cvPoint(20,30), cvScalar(255,255,255) );

Hình 10. Quan hệ kế thừa giữa các cấu trúc trong OpenCV.

Hình trên thể hiện mô hình phân lớp của 3 cấu trúc ảnh, thƣờng sử dụng nhất là IplImage. IplImage là cấu trúc cơ bản để mã hóa, lƣu trữ ảnh. Ảnh có thể là ảnh mức sáng – mức xám, ảnh màu hay ảnh 4 kênh với các thành phần RBG và alpha với mỗi kênh đƣợc biểu diễn bằng số nguyên hay số thực. OpenCV cung cấp rất nhiều các công cụ hữu ích để xử lý các ảnh này nhƣ thay đổi kích thƣớc(co, dãn), lấy giá trị lớn nhất, nhỏ nhất trên một kênh, lấy mẫu,…Cấu trúc dữ liệu cha của IplImage là CvMat biểu diễn cấu trúc mảng trong OpenCV. Vì OpenCV đƣợc lập trình bằng ngôn ngữ C nên mối quan hệ giữa 2 cấu trúc này có thể hiểu nhƣ kế thừa trong C++. Nhƣ vậy, IplImage có thể kế thừa các phƣơng thức và dữ liệu từ CvMat. Và một lần nữa, CvMat lại đƣợc kế thừa từ CvArr là cách biểu diễn cấu trúc mảng. Chính do lý do này nên trong hầu hết các hàm đều khai báo cho phép truyền cấu trúc CvArr và nhƣ một hệ quả là chúng ta có thể truyền cả CvMat và IplImage giúp cho chƣơng trình thực sự mềm dẻo với ngƣời sử dụng.

2.1.1.1 Cấu trúc ma trận CvMat

Có hai điều cần chú ý trƣớc khi đi vào tìm hiểu về CvMat là: Thứ nhất, không có cấu trúc véc-tơ trong OpenCV nên nếu muốn tạo ra một véc-tơ thì chúng ta chỉ việc sử dụng một ma trận với một cột hoặc một dòng tùy theo mục đích sử dụng. Thứ hai, khái niệm ma trận trong OpenCV là một khái niệm trừu tƣợng hơn trong đại số tuyến tính. Đặc biệt là các thành phần của ma trận không đơn giản là các số bình thƣờng. Ví dụ lệnh tạo ra một ma trận 2 chiều sẽ có dạng sau:

cvMat* cvCreateMat ( int rows, int cols, int type ); Ở đây type có thể đƣợc định nghĩa dƣới dạng sau:

Chính vì vậy, ma trận có thể gồm các số thực 32 bit (CV_32FC1), số nguyên dƣơng (CV_8UC3),… Hơn nữa, một phần tử của ma trận không nhất thiết phải là số. Nó có thể là các giá trị đƣợc biểu diễn phức tạp hơn cho phép biểu diễn ảnh RGB với nhiều kênh màu. Ví dụ nhƣ một ảnh đơn giản gồm có các thành phần đỏ, xanh lá cây, xanh da trời và các toán tử thao tác trên các thành phần ảnh này một cách độc lập. Để biểu diễn đƣợc ma trận nhƣ vậy thì cấu trúc dữ liệu của ma trận gồm có thành phần chiều rộng, chiều cao, kiểu mỗi phần tử, bƣớc nhảy (độ rộng một dòng tính bằng byte) và con trỏ tới mảng dữ liệu. Chúng ta có thể truy cập vào các phần tử một cách trực tiếp thông qua các con trỏ tham chiếu tới CvMat. Để lấy kích thƣớc ma trận ta có thể dùng lời gọi cvGetSize(CvMat*) trả về cấu trúc kiểu CvSize với giá trị height và width chƣa chiều cao và chiều rộng của ma trận hoặc trực tiếp qua lời gọi matrix->height and matrix->width. Cấu trúc CvMat đƣợc mô tả một các cụ thể nhƣ sau:

typedef struct CvMat { int type; int step; int* refcount; union { uchar* ptr; short* s; int* i; float* fl; double* db; } data; union { int rows; int height; }; union {

int cols; int width; };

} CvMat;

Ma trận có thể tạo bằng nhiều cách, cách đơn giản nhất là dùng cvCreateMat() kết hợp từ các hàm cơ sở nhƣ cvCreateMatHeader() và cvCreateData(). cvCreateMatHeader() tạo ra cấu trúc CvMat nhƣng chƣa đƣợc cấp phát bộ nhớ trong khi cvCreateData() thực hiện cấp phát bộ nhớ. Đôi khi chúng ta chỉ sử dụng cvCreateMatHeader() vì không gian nhớ đã đƣợc thiết lập ở đâu đó. Một phƣơng thức khác là sử dụng cvCloneMat(CvMat*) để tạo ra một ma trận mới từ một ma trận đã tồn tại. Khi không sử dụng tới ma trận nữa, ta có thể giải phóng khỏi bộ nhớ bằng lệnh cvReleaseMat(CvMat**). Dƣới đây là chi tiết các lời gọi của các hàm trên:

CvMat* cvCreateMat( int rows, int cols, int type );

CvMat* cvCreateMatHeader( int rows, int cols, int type ); CvMat* cvInitMatHeader(

CvMat* mat, int rows, int cols, int type,

void* data = NULL,

int step = CV_AUTOSTEP );

CvMat cvMat( int rows, int cols, int type,

);

CvMat* cvCloneMat( const cvMat* mat ); void cvReleaseMat( CvMat** mat );

Có nhiều cách để truy cập dữ liệu nhƣ CvMatrix. Các đơn giản nhất là sử dụng lệnh CV_MAT_ELEM(). Lênh này nhận lối vào mà ma trận với kiểu và vị trí phần tử cần lấy. Ví dụ nhƣ sau:

CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );

float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 );

Nếu muốn lấy về con trỏ tới dữ liệu cần truy xuất có thể sử dụng CV_MAT_ELEM_PTR(…) với các tham số lối vào là ma trận, dòng và cột của phần tử cần lấy.

CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 ); float element_3_2 = 7.7;

*( (float*)CV_MAT_ELEM_PTR( *mat, 3, 2 ) ) = element_3_2; Không may là các macro này không tính lại con trỏ cấn thiết cho mọi lời gọi. Điều này nghĩa là chúng ta chỉ nên lấy giá trị con trỏ tới phần tử đầu tiên của vùng dữ liệu ma trận và tự tính độ lệch tới các phần tử tiếp theo. Chính vì vậy, mặc dù macro này khác đơn giản nhƣng lại không phải là cách tốt nhất để truy xuất ma trận.

Một các hiệu quả nhất để truy xuất đó là chúng ta tự quản lý con trỏ truy cập vào dữ liệu của ma trận nhƣ ví dụ dƣới đây:

float sum( const CvMat* mat ) { float s = 0.0f;

for(int row=0; row<mat->rows; row++ ) {

const float* ptr = (const float*)(mat->data.ptr + row * mat->step); for( col=0; col<mat->cols; col++ ) {

s += *ptr++; }

}

return( s ); }

Khi tính giá trị con trỏ tới ma trận thì chúng ta cần lƣu ý rằng dữ liệu các phần tử ma trận là kiểu union. Chính vì vậy nên khi tham chiếu cần chỉ rõ chính xác union nào đƣợc sử dụng để có đƣợc giá trị con trở chính xác. Để tính đƣợc độ lệch của con trỏ thì chúng ta sử dụng thuộc tính step – đã đƣợc mô tả ở phần trên – và height, width.

2.1.1.2 Cấu trúc dữ liệu IplImage

Với những kiến thức đã đề cập ở phần trên, bây giờ ta có thể tiếp cận cấu trúc dữ liệu IplImage một cách dễ dàng. Nhƣ đã nói ở trên, IplImage đƣợc kế thừa từ CvMat và có khai báo cụ thể nhƣ sau:

typedef struct _IplImage { int nSize; int ID; int nChannels; int alphaChannel; int depth; char colorModel[4]; char channelSeq[4]; int dataOrder; int origin; int align; int width; int height;

struct _IplROI* roi;

void* imageId;

struct _IplTileInfo* tileInfo; int imageSize; char* imageData; int widthStep; int BorderMode[4]; int BorderConst[4]; char* imageDataOrigin; } IplImage;

Ngoài các thuộc tính dễ nhận thấy nhƣ width, height là các thuộc tính depth, nChannel, alphaChannel,..liên quan tới tính chất của ảnh nhƣ giá trị biểu diễn một điểm, số kênh ứng với không gian màu,… Depth có thể mang các giá trị dƣới đây:

Các giá trị depth Ý nghĩa

IPL_DEPTH_8U Số nguyên không dấu 8 bit IPL_DEPTH_8S Số nguyên có dấu 8 bit IPL_DEPTH_16S Số nguyên có dấu 16 bit IPL_DEPTH_32S Số nguyên có dấu 32 bit

IPL_DEPTH_32F Số thực có độ chính xác đơn 32 bit IPL_DEPTH_64F Số thực có độ chính xác đơn 64 bit

Hai thuộc tính quan trọng khác là origin và dataOrder. Origin có thể nhận hai giá trị: IPL_ORIGIN_TL hoặc IPL_ORIGIN_BL. Giá trị này mô tả vị trí gốc của ảnh từ góc trên bên trái hay góc dƣới bên trái. Thuộc tính dataOrder có thể là IPL_DATA_ORDER_PIXEL hoặc IPL_DATA_ORDER_PLANE mô tả cách dữ liệu đƣợc ghép theo kiểu ghép xen điểm ảnh hay đƣợc phân nhóm thành các mặt phẳng. Thuộc tính widthStep là số byte giữa các điểm có cùng một cột(số byte dữ liệu ảnh trong một hàng giống nhƣ step của CvMat). Thuộc tính

imageData chứa con trỏ tới phần tử ảnh đầu tiên. Chú ý là việc truy cập dữ liệu tuần tự các thành phần RGB chỉ có thể thực hiện với thiết lập IPL_DATA_ORDER_PIXEL và khi đó số khái niệm số dòng, số cột của ma trận dữ liệu ảnh trùng với chiều dài và chiều rộng của ảnh. ROI là một thuộc tính chứa giá trị con trỏ của cấu trúc IPL/IPP với mục đích xử lý ảnh theo các vùng riêng biệt thay vì toàn cục.

Nhƣ đã trình bày đối với CvMat thì các truy xuất điểm ảnh hiệu quả nhất sẽ đƣợc trình bày nhƣ ví dụ dƣới đây:

void saturate_sv( IplImage* img ) {

for( int y=0; y<img->height; y++ ) {

uchar* ptr = (uchar*) (img->imageData + y * img->widthStep); for( int x=0; x<img->width; x++ ) {

ptr[3*x+1] = 255;//thành phần G ptr[3*x+2] = 255;//thành phần B }

} }

Đoạn chƣơng trình thực hiện truy xuất từng dòng của ảnh. Với mỗi dòng sẽ sử dụng con trỏ ptr để trỏ tới giá trị đầu tiên của hàng để đảm bảo việc truy xuất hoạt động tốt. Trong mỗi hàng sẽ tiến hành truy xuất từng điểm ảnh thông qua việc truy xuất từng thành phần RGB với ảnh RGB bằng cách cộng giá trị con trỏ với giá trị lệch thích hợp.

Nhƣ đã đề cập ở trên, CvMat và IplImage đều đƣợc kế thừa từ CvArr nên các toán tử đƣợc định nghĩa cho hai cấu trúc này thƣờng đƣợc định nghĩa qua CvArr gồm một số toán tử hay đƣợc sử dụng nhƣ sau:

Toán tử Ý nghĩa

cvAbs Giá trị tuyệt đối của tất cả các phần tử trong mảng cvAbsDiff Giá trị tuyệt đối sai khác giữa 2 mảng

cvAbsDiffS Giá trị tuyệt đối sai khác của một mảng với giá trị nhất định

cvAdd Cộng 2 mảng

cvAddS Cộng giá trị của mảng với một giá trị nhất định cvAddWeighted Cộng 2 mảng có trọng số

cvAvg Giá trị trung bình của tất cả các giá trị trong mảng

cvAvgSdv Giá trị tuyệt đối và độ lệch chuẩn của tất cả các giá trị trong mảng

cvCalcCovarMatrix Tính hiệp phƣơng sai của tập véc-tơ n chiều cvCmp So sánh giá trị phần tử của 2 mảng

cvCmpS So sánh giá trị phần tử của mảng với giá trị nhất định cvConvertScale Biến đổi kiểu mảng với giá trị tỉ lệ tùy chọn

cvConvertScaleAbs Biến đổi kiểu mảng sau khi lấy trị tuyệt đối với giá trị độ chia tùy chọn

cvCopy Sao chép toàn bộ giá trị của một mảng sang một mảng khác

cvCountNonZero Đếm số phần tử khác 0 trong mảng cvCrossProduct Tính tích chéo của 2 véc-tơ 3 chiều cvCvtColor Biến đổi giữa các hệ màu của ảnh cvDet Tính định thức của ma trận vuông cvDiv Chia ma trận này cho một ma trận khác cvDotProduct Tính tích nội giữa 2 véc-tơ

cvGetCol Lấy phần tử thuộc một cột của mảng cvGetCols Lấy phần tử thuộc các cột của mảng cvGetDiag Lấy các giá trị đƣờng chéo của mảng

cvGetDims Lấy kích thƣớc mảng

cvGetDimSize Lấy kích thƣớc của tất cả các chiều của mảng cvGetRow Lấy phần tử thuộc một dòng của mảng

cvGetRows Lấy phần tử thuộc các dòng của mảng cvGetSize Lấy kích thƣớc của mảng 2 chiều cvGetSubRect Lấy phần tử một vùng nhỏ của mảng

cvInRange Kiểm tra xem các giá trị của mảng có nằm trong mảng khác hay không

cvInRangeS Kiểm tra xem các giá trị của mảng có nằm giữa 2 giá trị đƣợc cho hay không

cvInvert Lấy nghịch đảo của ma trận vuông

cvMax Trả về giá trị lớn nhất của 2 giá trị phần tử của 2 mảng cvMaxS Trả về giá trị lớn nhất của giá trị phần tử của mảng với

một giá trị xác định

cvMerge Trộn nhiều kênh đơn của các ảnh thành một ảnh nhiều kênh

cvMin Trả về giá trị nhỏ nhất của 2 giá trị phần tử của 2 mảng cvMinS Trả về giá trị lớn nhất của giá trị phần tử của mảng với

một giá trị xác định

cvMinMaxLoc Tìm giá trị lớn nhất và nhỏ nhất trong mảng cvMul Nhân tƣơng ứng các phần tử của 2 mảng cvNot Đảo giá trị bit đối với mỗi phần tử mảng cvNorm Tình tƣơng quan chuẩn giữa hai mảng

cvNormalize Chuẩn hóa các giá trị của mảng đối với một giá trị cho trƣớc

mảng

cvOrS Thực hiện toán tử OR giữa phần tử của mảng với một giá trị cho trƣớc

cvReduce Giảm một mảng 2 chiều thành một véc-tơ theo toán tử cho trƣớc

cvRepeat Sắp xếp các giá trị của mảng này vào mảng khác

cvSet Thiết lập tất cả các giá trị của mảng thành một giá trị xác định

cvSetZero Thiết lập tất cả các phần tử mảng đều mang giá trị 0

cvSetIdentity Thiết lập tất cả các phần tử đƣờng chéo bằng 1 và các phần tử khác bằng 0

cvSplit Chia 1 mảng nhiều kênh thành nhiều mảng nhiều kênh cvSub Trừ tƣơng ứng các phần tử giữa hai mảng

cvSubS Trừ tƣơng ứng một giá trị cho từng phần tử mảng cvSubRS Trừ tƣơng ứng phần tử mảng với giá trị xác định cvSum Tính tổng tất cả các giá trị trong mảng

cvXor Thực hiện toán tử XOR giữa 2 phần tử tƣơng ứng của 2 mảng

cvXorS Thực hiện toán tử OR giữa phần tử của mảng với một giá trị cho trƣớc

cvZero Thiết lập tất cả các giá trị của mảng đều bằng 0

Một phần của tài liệu Thuật toán phát hiện chuyển động (Trang 45)