Quan hệ kế thừa giữa các cấu trúc trong OpenCV

Một phần của tài liệu (LUẬN văn THẠC sĩ) thuật toán phát hiện chuyển động (Trang 46 - 67)

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;

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

2.2 Thƣ viện ngƣời dùng đồ họa mức cao

Các hàm OpenCV cho phép chúng ta có thể giao tiếp với hệ điều hành, hệ thống tệp tin, phần cứng cũng nhƣ camera thông qua một thƣ viện gọi là HighGUI – giao tiếp ngƣời dùng đồ họa mức cao. HighGUI cho phép chúng ta mở các cửa sổ, hiển thị ảnh, đọc và ghi các tệp tin liên quan tới ảnh, video cũng nhƣ điều khiển chuột, con trỏ và các sự kiện bàn phím. Thƣ viện HighGUI của

OpenCV có thể chia ra làm 3 phần: phần cứng, hệ thống file, và phần giao diện ngƣời dùng đồ họa.

Phần liên quan tới phần cứng thƣờng liên quan tới camera. Trong hầu hết các hệ hiều hành, thì việc tƣơng tác với camera là một công việc không mấy thích thú. HighGUI cung cấp một cách dễ dàng để truy xuất và lấy ra ảnh cuối cùng thu đƣợc, ẩn đi các công việc nhàm chán cần thiết phải làm. Phần hệ thống file thì thƣờng liên quan tới việc nạp và lƣu trữ các ảnh. Một đặc điểm khá thú vị của thƣ viện này là cho phép chúng ta đọc các đoạn video với cùng phƣơng thức giống nhƣ trực tiếp từ camera. Chính vì vậy, chúng ta có thể tạm quên đi các công việc liên quan tới phần cứng. Cũng với cùng một tinh thần đó, HighGUI cũng cấp cho chúng ta các hàm để nạp và lƣu trữ ảnh. Căn cứ vào cấu trúc tệp ảnh lƣu đƣợc lƣu trữ, nó có thể tự xác định cách mã hóa và giải mãi thích hợp với cấu trúc ảnh đó. Và phần cuối cùng là giao diện ngƣời dùng đồ họa. Thƣ viện này cũng cấp các hàm cho phép chúng ta mở một cửa sổ mới và hiển thị các ảnh một cách dễ dàng. Không chỉ có vậy, chúng ta có thể đang ký và nhận các đáp ứng trả về cho các sự kiện chuột và bàn phím thao tác trên cửa sổ đó. Đây là một việc không phải dễ dàng với các ứng dụng cơ bản. Hay chúng ta cũng có thể sử dụng các thanh trƣợt để điều chỉnh các giá trị cho chƣơng trình, thời gian hiển thị đoạn video,…

Trƣớc khi có thể hiển thị một ảnh trên màn hình sử dụng HighGUI thì chúng ta cần thiết lập cửa sổ hiển thị bằng lời gọi cvNamedWindow(). Hàm này nhận 2 tham số lối vào là tên cửa số vả một giá trị cờ. Tên cửa sổ đƣợc dùng để hiển thị trên dòng tiêu đề, để điều khiển các thông điệp cửa sổ cũng nhƣ đƣợc truyền cho các hàm HighGUI khác. Giá trị cờ xác định rằng cửa sổ có tự động co giãn kích thƣớc cho vừa với bức ảnh hiển thị trong nó hay không. Và đây là mô tả nguyên mẫu hàm cụ thể nhƣ sau

int cvNamedWindow( const char* name,

int flags = CV_WINDOW_AUTOSIZE );

Sau khi đã sử dụng xong cửa sổ, ta có thể giải phóng nó bằng lời gọi cvDestroyWindow() với tham số là tên cửa sổ giống nhƣ trong phần thiết lập. Trong OpenCV, các cửa sổ đƣợc tham chiếu đến thông qua tên cửa sổ thay vì

nhau giữa các hệ điều hành. Đây cũng là một ƣu điểm lớn của OpenCV đối với các nhà nghiên cứu, những ngƣời sử dụng nó.

Muốn hiện thị đƣợc ảnh thì trƣớc đó chúng ta phải đọc nó từ tệp tin bằng lời gọi:

IplImage* cvLoadImage( const char* filename,

int iscolor = CV_LOAD_IMAGE_COLOR );

Khi mở một ảnh, cvLoadImage() không quan tâm tới phần mở rộng của tệp tin – vì nó có thể bị thay đổi một cách dễ dàng. Thay vào đó, nó sẽ căn cứ vào các byte mã hóa đầu tiên để biết đƣợc bộ mã hóa-giải mã thích hợp để sử dụng. Tham số thứ hai xác định cách thức nạp ảnh vào IplImage, cụ thể nhƣ sau:

Giá trị Ý nghĩa

CV_LOAD_IMAGE_COLOR

Một phần của tài liệu (LUẬN văn THẠC sĩ) thuật toán phát hiện chuyển động (Trang 46 - 67)

Tải bản đầy đủ (PDF)

(86 trang)