CHAPTER 3 Getting to know OpenCV OpenCV Primitive Data Types OpenCV có vài data type cơ sở. Những data type này không cơ sở từ góc nhìn của C, nhưng chúng tất cả là các structure đơn giản, và ta sẽ xem chúng như nguyên tố. Bạn có thể xem xét chi tiết các structure mô tả như sau (cũng như các structure khác) trong cxtypes.h header file, mà ở .../OpenCV/cxcore/include directory của cài cặt OpenCV. Đơn giản nhất của những types này là CvPoint. CvPoint là structure đơn giản với hai integer members, x và y. CvPoint có hai anh em: CvPoint2D32f và CvPoint3D32f. Cái đầu có cùng hai members x và y, mà cả hai là các số floating-point. Cái sau cũng chứa phần tử thứ ba, z. CvSize giống anh họ của CvPoint. Các phần tử của nó là width và height, mà cả hai là integer. Nếu bạn muốn các số floating-point, dùng anh học CvSize2D32f cousin của CvSize. CvRect một con khác của CvPoint và CvSize; nó chứa bốn member: x, y, width, và height. (Trong trường hợp bạn lo lắng, con cháu này được chấp nhận.) Cuối cùng nhưng không kém là CvScalar, mà là một tập bốn số double-precision. Khi memory không là vấn đề, CvScalar thường được dùng để biểu diễn một, hai, hay ba số thực (trong những trường hợp này, các thành phần không cần đơn giản được bỏ qua). CvScalar có một member val, mà là một pointer đến một array chứa bốn số double-precision floating-point. Tất cả những data type này có các constructor method có nên như cvSize() (nhìn chung* constructor có cùng tên như structure type nhưng với character đầu tiên không hoa). Nhớ rằng đây là C và không phải C++, do đó những “constructors” này chỉ là các inline function mà nhận một list các argument và return structure mong muốn với các value được đặt thích hợp. Các inline constructor cho các data type kê trong Table 3-1—cvPointXXX(), cvSize(), cvRect(), và cvScalar()—là cực kỳ hữu ích vì chúng làm code không chỉ dễ hơn để viết mà còn dễ đọc hơn. Giả sử bạn muốn vễ một rectangle trắng giữa (5, 10) và (20, 30); bạn có thể đơn giản gọi: cvRectangle( myImg, cvPoint(5,10), cvPoint(20,30), cvScalar(255,255,255) ); Table 3-1. Structures for points, size, rectangles, và scalar tuples Structure chứa Represents CvPoint int x, y Point in image CvPoint2D32f float x, y Points in ℜ2 CvPoint3D32f float x, y, z Points in ℜ3 CvSize int width, height Size of image CvRect int x, y, width, height Portion of image CvScalar double val[4] RGBA value cvScalar() là trường hợp đặc biệt: nó có ba constructor. Đầu tiên, gọi là cvScalar(), lấy một, hai, ba, hay bốn argument và gán những argument này cho các phần tử tương ứng của val[]. Constructor thứ hai là cvRealScalar(); nó lấy một argument, mà nó gán vào val[0] trong khi setting các entry khác thành 0. Variant cuối cùng là cvScalarAll(), mà lấy một argument đơn nhưng đặt tất cả bốn phần tử của val[] thành cùng argument đó. Matrix và Image Types Hình 3-1 cho thấy thứ bậc class hay structure của ba image types. Khi dùng OpenCV, bạn sẽ chạm trán lặp lại IplImage data type. Bạn hiện đã thấy nó nhiều lần trong chương trước. IplImage là structure cơ sở được dùng để encode cái ta nhìn chung gọi là “các image”. Những image này có thể là grayscale, color, four-channel (RGB+alpha), và mỗi channel có thể chứa bất kỳ trong vài type của các số integer hay floatingpoint. Do đó, kiểu này là chung hơn image three-channel 8-bit RGB có khắp nơi mà tức thời đến trong đầu.* OpenCV cung cấp lượng lớn các operator hữu ích mà hoạt động trên những images này, gồm các tool để resize images, trích các channel riêng, tìm thấy các value lớn nhất hay nhỏ nhất của một channel cụ thể, cộng hai images, threshold một image, và hơn nữa. In chương này ta sẽ xem xét những kiểu này của các operator cẩn thận. Hình 3-1. Ngay cả qua OpenCV được thực hiện trong C, các structure được dùng trong OpenCV có một thiết kế hướng đối tượng; về hiệu quả, IplImage được dẫn từ CvMat, mà được dẫn từ CvArr Trước khi ta có thể thảo luận các image chi tiết, ta cần quan sát một data type khác: CvMat, OpenCV matrix structure. Dù OpenCV được thực hiện toàn bộ trong C, quan hệ giữa CvMat và IplImage là hơi giống với thứ bậc trong C++. Cho tất cả các nghĩa và các mục đích, IplImage có thể được nghĩ như được dẫn từ CvMat. Do đó, điều tốt nhất để hiểu là lớp cơ sở (nên là) trước khi cố hiểu các phức tạp thêm của lớp dẫn xuất. Lớp thứ ba, gọi là CvArr, có thể được nghĩ như một abstract base class mà từ đó CvMat tự nó được dẫn. Bạn sẽ thường thấy CvArr (hay, chính xác hơn, CvArr*) trong các function prototypes. Khi nó xuất hiện, nó chấp nhận chuyển CvMat* hay IplImage* cho routine. CvMat Matrix Structure Có hai điều bạn cần biết trước khi ta chìm vào matrix business. Đầu tiên, không có “vector” construct trong OpenCV. Bất cứ khi nào ta muốn một vector, ta chỉ dùng một matrix với một cột (hay một hàng, nếu ta muoons một hoán vị hay kết hợp vector). Thứ hai, khái niệm của một matrix trong OpenCV là có phần trừu tượng hơn khái niệm bạn học trong lớp đại số tuyền tính. Về cụ thể, các phần tử củmột matrix tự chúng không là các số đơn giản. Ví dụ, routine mà mà tạo một two-dimensional matrix mới có prototype sau: cvMat* cvCreateMat ( int rows, int cols, int type ); Ở đây type có thể là bất kỳ của một list dài các predefined type theo dạng: CV_<bit_depth>(S|U|F) C<number_of_channels>. Do đó, matrix có thể gồm các 32-bit floats (CV_32FC1), của các unsigned integer 8-bit triplets (CV_8UC3), hay vô số các element khác. Một element của CvMat không cần thiết là một số đơn. Có thể biểu diễn nhiều giá trị cho một entry đơn trong matrix cho phép ta làm nhiều thứ như biểu diễn nhiều channel màu trong một RGB image. Cho một image đơn giản chứa các kênh red, green và blue, hầu hết các image operator sẽ được áp dụng cho mỗi channel riêng (trừ phi ngược lại được lưu ý). Về bên trong, structure của CvMat tương đối đơn giản, như được thấy trong Example 3-1 (bạn có thể thấy điều này bởi mở …/opencv/cxcore/include/cxtypes.h). Các matrix có một width, height, type, step (chiều dài của một row theo các byte, không ints hay floats), và một pointer đến một data array (và một vài thứ mà ta chưa thể nói). Bạn có thể truy cập những members này trực tiếp bởi referencing lại một pointer đến CvMat hay, cho một vài element phổ biến hơn, bởi dùng các accessor function được cung cấp. Ví dụ, để lấy size của một matrix, bạn có thể lấy thông tin bạn muốn một trong bởi gọi cvGetSize(CvMat*), mà trả về một CvSize structure, hay bởi truy cập height và width độc lập với các constructs như matrix->height và matrix>width. Example 3-1. CvMat structure: the matrix “header” typedef struct CvMat { int type; int step; int* refcount; // cho dùng chỉ bên trong union { uchar* ptr; short* s; int* i; float* fl; double* db; } data; union { int rows; int height; }; union { int cols; int width; }; } CvMat; Thông tin này nhìn chung được refer đến như matrix header. Nhiều routines phân biệt giữa header và data, cái sau là memory mà data element trỏ đến. Các matrix có thể được tạo theo một trong vài cách. Các hầu hết phổ biến là dùng cvCreateMat(), mà is thực sự thuận tiện để sự kết hợp của nhiều atomic function cvCreateMatHeader() và cvCreateData(). cvCreateMatHeader() tạo CvMat structure không cấp memory cho data, trong khi cvCreateData() handles cấp phát data. Đôi khi chỉ cvCreateMatHeader() được đòi hỏi, một trong vì bạn đã cấp data cho vài nguyên nhân khác hay vì bạn chưa thực sự cần cấp nó. Method thứ ba để dùng cvCloneMat(CvMat*), mà tạo matrix mới từ một cái hiện có.* Khi matrix không còn cần nữa, nó có thể được giải phóng bởi gọi cvReleaseMat(CvMat**). List trong Example 3-2 tóm tắt các function ta vừa mô tả cũng những cái khác mà liên quan mật thiết. Example 3-2. Matrix creation và release // tạo một matrix rows by cols của kiểu ‘type’. // CvMat* cvCreateMat( int rows, int cols, int type ); // tạo only matrix header without allocating data // CvMat* cvCreateMatHeader( int rows, int cols, int type ); // Khởi tạo header on existing CvMat structure // CvMat* cvInitMatHeader( CvMat* mat, int rows, int cols, int type, void* data = NULL, int step = CV_AUTOSTEP ); // Like cvInitMatHeader() but allocates CvMat as well. // CvMat cvMat( int rows, int cols, int type, void* data = NULL ); // Allocate a new matrix just like the matrix ‘mat’. // CvMat* cvCloneMat( const cvMat* mat ); // Free the matrix ‘mat’, both header và data. // void cvReleaseMat( CvMat** mat ); Tương tự với nhiều OpenCV structures, có một constructor gọi là cvMat() mà tạo một CvMat structure. Routine này không thực sự cấp phát memory; nó chỉ tạo header (điều này tương tự với cvInitMatHeader()). Những method này là cách tốt để lấy vài data bạn đã có nằm dưới, đóng gói nó bởi trỏ matrix header đến nó như trong Example 3-3, và chạy nó qua các routine mà xử lý các OpenCV matrice. Example 3-3. Tạo một OpenCV matrix với data cố định // tạo an OpenCV Matrix containing some fixed data. // float vals[] = { 0.866025, -0.500000, 0.500000, 0.866025 }; CvMat rotmat; cvInitMatHeader( &rotmat, 2, 2, CV_32FC1, vals ); Một khi ta có một matrix, có nhiều thứ ta có thể làm với nó. Các tác vụ đơn giản nhất là query các khía cạnh của định nghĩa array và truy cập data. Để query matrix, ta có cvGetElemType( const CvArr* arr ), cvGetDims( const CvArr* arr, int* sizes=NULL ), và cvGetDimSize( const CvArr* arr, int index ). Cái đầu tiên trả về một integer constant biểu diễn kiểu các phần tử được lưu trong array (điều này sẽ bằng với cái giống CV_8UC1, CV_64FC4, …). Cái thứ hai lấy array và một pointer tùy chọn đến một integer; nó trả về số các chiều (hai cho trường hợps ta đang quan tâm, nhưng về sau ta sẽ trạm chán các N-dimensional matrix như các object). Nếu integer pointer là khác null thì nó sẽ lưu height và width(hay N dimensions) của array được cung cấp. Function cuối lấy một integer nhận biết chiều của cái quan tâm và đơn giản trả về extent của matrix ở trong chiều đó.* Accessing Data in Your Matrix Có ba cách để truy cập data trong matrix: cách dễ, cách khó, và cách đúng. The easy way Cách dễ nhất để lấy một phần tử thành phần của một array là bằng CV_MAT_ELEM() macro. Macro này (xem Example 3-4) lấy matrix, type của phần tử được nhận, và số row và column và sau đó trả về phần tử. Example 3-4. Trruy cập một matrix bằng CV_MAT_ELEM() macro CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 ); float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 ); “Under the hood” macro này chỉ gọi macro CV_MAT_ELEM_PTR(). CV_MAT_ELEM_PTR() (xem Example 3-5) lấy các argument matrix và row và column của element được quan tâm và trả về (không ngạc nhiên) một pointer đến phần tử được chỉ định. Một khác biệt quan trọng giữa CV_MAT_ELEM() và CV_MAT_ELEM_PTR() đó là CV_MAT_ELEM() thực sự ép pointer thành kiểu chỉ định trước khi de-referencing nó. Nếu bạn thích đặt giá trị hơn chỉ đọc nó, bạn có thể gọi CV_MAT_ELEM_PTR() trực tiếp; trong trường hợp này, tuy nhiên, bạn phải cast pointer trả về thành kiểu thích hợp. Example 3-5. Đặt một giá trị đơn trong matrix dùng CV_MAT_ELEM_PTR() macro 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, những cái này macros tính lại pointer cần trong mỗi lời gọi. Điều này có nghĩa giám sát pointer thành phần tử cơ sở của vùng data của matrix, tính một offset để lấy address của thông tin bạn quan tâm, và sau đó thêm offset đó vào cơ sở được tính. Do đó, mặc dù những macro này là dễ dùng, chúng có thể không là cách tốt nhất để truy cập một matrix. Điều này là đặc biệt đúng khi bạn có kế hoạch truy cập tất cả các element trong một matrix tuần tự. Ta sẽ đến ngay với cách tốt nhất để hoàn thành nhiệm vụ quan trọng này. The hard way Hai macros được thảo luận trong “The easy way” chỉ phù hợp để truy cập các mảng một hay hai chiều (nhớ lại các mảng một chiều, hay “các vector”, thực sự chỉ là n-by-1 các matrix). OpenCV cung cấp các cơ chế để làm việc với các mảng đa chiều. Thật ra OpenCV cho phép cho một Ndimensional matrix nói chung mà có thể có nhiều chiều như bạn thích. Để truy cập data một matrix tổng quát, ta dùng họ các function cvPtr*D và cvGet*D… kê trong Examples 3-6 và 3-7. cvPtr*D family chứa cvPtr1D(), cvPtr2D(), cvPtr3D(), và cvPtrND() . . . . Mỗi cái trong ba cái đầu tiên lấy một tham số pointer matrix CvArr* theo sau bởi số thích hợp của các integer cho các index, và một argument tùy chọn nhận diện kiểu của tham số output. Các routine return một pointer đến element quan tâm. Bằng cvPtrND(), argument thứ hai là một pointer đến một array các integer chứa số thích hợp của các index. Ta sẽ return function này sau. (Trong các prototype mà theo sau, bạn sẽ cũng llwu ý các argument tùy chọn thích hợp; ta sẽ address những cái này khi ta cần chúng.) Example 3-6. Pointer truy cập vào các matrix structures uchar* cvPtr1D( const CvArr* arr, int idx0, int* type = NULL ); uchar* cvPtr2D( const CvArr* arr, int idx0, int idx1, int* type = NULL ); uchar* cvPtr3D( const CvArr* arr, int idx0, int idx1, int idx2, int* type = NULL ); uchar* cvPtrND( const CvArr* arr, int* idx, int* type = NULL, int create_node = 1, unsigned* precalc_hashval = NULL ); Để đọc hoàn toàn data, có một dòng khác của các function cvGet*D, kê trong Example 3-7, mà tương tự với những cais trong Example 3-6 nhưng return giá trị thực của matrix element. Example 3-7. CvMat và IplImage element functions double cvGetReal1D( const CvArr* arr, int idx0 ); double cvGetReal2D( const CvArr* arr, int idx0, int idx1 ); double cvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 ); double cvGetRealND( const CvArr* arr, int* idx ); CvScalar cvGet1D( const CvArr* arr, int idx0 ); CvScalar cvGet2D( const CvArr* arr, int idx0, int idx1 ); CvScalar cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 ); CvScalar cvGetND( const CvArr* arr, int* idx ); Return type của cvGet*D là double cho bốn của các routine và CvScalar cho bốn cái khác. Điều này có nghĩa rằng có thể có vài lãng phí có ý nghĩa khi dùng những function này. Chúng nên được dùng chỉ nơi thuận lợi và hiệu quả; ngược lại, tốt hơn là dùng cvPtr*D. Một nguyên nhân tốt hơn để dùng cvPtr*D() là bạn có thể dùng những pointer functions này để có lợi truy cập đến một điểm cụ thể trong matrix và sau đó dùng đại số pointer để di chuyển quanh matrix từ đó. Điều quan trọng phải nhớ rằng các channel là liên tiếp trong multichannel matrix. Ví dụ, trong matrix hai chiều ba channel biểu diễn các byte red, green, blue (RGB), matrix data được lưu: rgbrgbrgb . . . . Do đó, để di chuyển một pointer của kiểu thích hợp đến channel tiếp theo, ta cộng 1. Nếu ta muốn đi đến “pixel” tiếp theo hay tập các element, ta cộng và offset bằng với số các channel (trong trường hợp này là 3). Mẹo khác để biết phần tử bước trong matrix array (xem Examples 3-1 và 3-3) là chiều dài theo bytes của một row trong matrix. Trong structure đó, các cột hay một mình độ rộng không đủ để di chuyển giữa các hàng matrix vì, để hiện quả máy, matrix hay image allocation được làm thành biên bốn byte gần nhất. Do đó một matrix của ba byte rộng sẽ được cấp thành bốn byte với cái cuối được bỏ qua. Cho nguyên nhân này, nếu ta lấy một byte pointer đến data element thì ta thêm bước vào pointer để chuyển nó đến row tiếp theo trực tiếp bên dưới điểm của tapoint. Nếu ta có một matrix các số nguyên hay floating-point và tương ứng int hay float pointers cho một data element, ta sẽ bước đến hàng tiếp theo bởi thêm step/4; cho các double, ta cộng step/8 (điều này chỉ để lấy một lượng mà C sẽ tự động nhân các offset ta cộng bởi byte size của data type). Một ít tương tự với cvGet*D là cvSet*D trong Example 3-8, mà đặt một matrix hay image element bằng một lời gọi, và các functions cvSetReal*D() và cvSet*D(), mà có thể được dùng để set các value của các elements của một matrix hay image. Example 3-8. Set element functions for CvMat hay IplImage. void cvSetReal1D( CvArr* arr, int idx0, double value ); void cvSetReal2D( CvArr* arr, int idx0, int idx1, double value ); void cvSetReal3D( CvArr* arr, int idx0, int idx1, int idx2, double value ); void cvSetRealND( CvArr* arr, int* idx, double value ); void cvSet1D( CvArr* arr, int idx0, CvScalar value ); void cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value ); void cvSet3D( CvArr* arr, int idx0, int idx1, int idx2, CvScalar value ); void cvSetND( CvArr* arr, int* idx, CvScalar value ); Như một thuận lợi đi kèm, ta cũng có cvmSet() và cvmGet(), mà được dùng khi làm việc với các matrix một channel floating-point. Chúng là rất đơn giản: double cvmGet( const CvMat* mat, int row, int col ) void cvmSet( CvMat* mat, int row, int col, double value ) Do đó lời gọi đến function tiện lợi cvmSet(), cvmSet( mat, 2, 2, 0.5000 ); là giống với lời gọi đến cvSetReal2D function tương đương, cvSetReal2D( mat, 2, 2, 0.5000 ); The right way Với tất cả các accessor function này, bạn có thể nghĩ rằng không gì thêm để nói. Thật ra, bạn sẽ hiếm khi dùng bất kỳ trong các hàm set và get. Hầu hết thời gian, vision là hoạt động đòi hỏi processor, và bạn sẽ muốn làm các thứ trong hầu hết cách hiệu quả có thể. Không cần thiết để nói, đi qua những interface functions này là không hiệu quả. Thay vào đó, bạn nên làm đại số pointer riêng và đơn giản de-reference cách của bạn vào matrix. Quản lý các pointers tự bạn là đặc biệt quan trọng khi bạn muốn làm gì đó với mọi element trong một array (giả thiết không có OpenCV routine mà có thể thực hiện nhiệm vụ này cho bạn). Để truy cập trực tiếp vào phần bên trong của một matrix, tất cả bạn thực sự cần biết là data được lưu tuần tự theo thứ tự quét thô, trong đó các cột (“x”) là biến chạy nhanh nhất. Các channel được chen vào, mà có nghĩa rằng, trong trường hợp của một multichannel matrix, chúng là thứ tự chạy nhanh nhất. Example 3-9 cho thấy một ví dụ cách điều này có thể được làm. Example 3-9. Cộng tất cả các elements trong một three-channel matrix 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 pointer trong matrix, nhớ rằng matrix element data là hợp nhất. Do đó, khi de-referencing pointer này, bạn phải nhận diện đúng element của hợp nhất này để để lấy pointer type đúng. Sau đó, offset pointer đó, bạn phải dùng step element của matrix. Như lưu ý trước đây, step element là theo byte. Để an toàn, tốt nhất để làm đại số pointer theo byte và sau đó cast type thích hợp, trong trường hợp này là float. Mặc dù CVMat structure có khái niêm height và width để tương thích với IplImage structure cũ, ta dùng the các hàng và cột cập nhật hơn thay vào. Cuối cùng, lưu ý rằng ta tính lại ptr cho mỗi hàng hơn là đơn giản bắt đầu ở lúc bắt đầu và sau đó tăng pointer đó mỗi lần đọc. Điều này có thể dường như dư, nhưng vì CvMat data pointer có thể chỉ trỏ đến một ROI với trong một array lớn hơn, không có đảm bảo data sẽ liên tiếp với các hàng. Arrays of Points Một vấn đề mà sẽ đến thường xuyên—và là quan trọng để hiểu—là khác nhau giữa một multidimensional array (hay matrix) of multidimensional objects và một array của một chiều cao hơn mà chứa chỉ các one-dimensional object. Giả thiết, ví dụ, bạn có n points theo ba chiều mà bạn muốn chuyển qua OpenCV function mà nhận một argument kiểu CvMat* (hay, đúng hơn, cvArr*). Có bốn cách hiển nhiên bạn có thể làm điều này, và nó tuyệt đối nghiêm túc để nhớ rằng chúng không cần thiết tương đương. Một method sẽ là dùng một two-dimensional array của type CV32FC1 với n rows và ba columns (n-by-3). Tương tự, bạn có thể dùng một two-dimensional array của ba row và n columns (3-by-n). Bạn có thể cũng dùng một array với n rows và một column (n-by-1) của type CV32FC3 hay một array với một row và n columns (3-by-1). Một vài trong những trường hợp này có thể được chuyển tự do từ cái sang cái khác (có nghĩa bạn có thể chỉ chuyển một nơi cái khác được mong) nhưng những cái khác không thể. Để hiểu lý do, quan sát memory layout được thấy trong Hình 3-2. Như bạn có thể thấy trong hình, các point được ánh xạ trong memory theo cùng cách cho ba trong bốn trường hợp vừa được mô tả ở trên nhưng khác cho cái cuối. Hình 3-2. Một tập mười point, mỗi cái được biểu diễn bởi ba số floating-point, đặt trong bốn array mà mỗi cái dùng một structure hơi khác; trong ba trường hợp memory layout kết quả là đồng nhất, nhưng trường hợp một là khác Trường hợp ngay cả phức tạp hơn cho trường hợp của một N-dimensional array của các cdimensional point. Điều cốt yếu là vị trí của bất kỳ point cho trước được cho bởi bởi công thức: (row) N cols N channels (col ) N channels (channel ) trong đó Ncols và Nchannels là số columns và channels, tương ứng.* Từ công thức này cái có thể thấy rằng, nhìn chung, một N-dimensional array của các c-dimensional object là không giống như một (N + c)-dimensional array của các one-dimensional object. Trong trường hợp đặc biệt N = 1 (chẳng hạn các vectors biểu diễn một trong như n-by-1 hay 1-by-n arrays), có một suy biến đặc biệt (đặc biệt, các tương đương được thấy trong Hình 3-2) mà có thể đôi khi có được thuận lợi về hiệu suất. Chi tiết cuối liên qua các OpenCV data type chẳng hạn CvPoint2D và CvPoint2D32f. Những data type này được định nghĩa như các C structure và do đó có memory layout được định nghĩa nghiêm chỉnh. Về cụ thể, các số integer hay floating-point mà những structures này hợp thành là thứ tự “channel”. Như một kết quả, một one-dimensional C-style array của các object này có cùng memory layout như một n-by-1 hay một 1-by-n array của type CV32FC2. Lý do tương tự áp dụng cho các array của các structure của type CvPoint3D32f. IplImage Data Structure/*/ Với tất cả điều đó trong tay, bây giờ dễ dàng thảo luận IplImage data structure. Trong object thực chất này là một CvMat nhưng với vài goodies thêm được đặt trong nó để làm matrix thích hợp cho một image. Structure này ban đầu được định nghĩa như một phần của Image Processing Library (IPL) của Intel.* Định nghĩa chính xác của IplImage structure được thấy trong Example 310. Example 3-10. IplImage header structure 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; struct _IplImage* maskROI; void* imageId; struct _IplTileInfo* tileInfo; int imageSize; char* imageData; int widthStep; int BorderMode[4]; int BorderConst[4]; char* imageDataOrigin; } IplImage; Nghe thật hay, ta muốn thảo luận chức năng của vài trong những variable này. Vài là bình thường, nhưng nhiều là rất quan trọng để hiểu cách OpenCV thông dịch và làm việc với các image. Sau width và height, depth và nChannels phổ biến là hầu hết chủ yếu tiếp theo. Biến depth lấy một trong một tập các value định nghĩa trong ipl.h, mà (không may) không chính xác các value ta trạm chán khi quan sát các matrix. Điều này vì cho các image ta hướng đến làm việc với depth và số các channel riêng rẽ (bất cứ matrix routines ta hướng đến refer chúng đồng thời). Các depth cth được kê trong Table 3-2. Table 3-2. OpenCV image types Macro IPL_DEPTH_8U IPL_DEPTH_8S IPL_DEPTH_16S IPL_DEPTH_32S IPL_DEPTH_32F IPL_DEPTH_64F Image pixel type Unsigned 8-bit integer (8u) Signed 8-bit integer (8s) Signed 16-bit integer (16s) Signed 32-bit integer (32s) 32-bit fl oating-point single-precision (32f) 64-bit fl oating-point double-precision (64f) Các value có thể cho nChannels là 1, 2, 3, hay 4. Hai members quan trọng tiếp theo là origin và dataOrder. Origin variable có thể lấy một trong hai value: IPL_ORIGIN_TL hay IPL_ORIGIN_BL, tương ứng bới gốc tọa độ được đặt một trong upperleft hay lower-left corners của image, tương ứng. Việc thiếu origin chuẩn (upper với lower) là nguồn quan trọng của error trong các computer vision routines. Về cụ thể, phụ thuộc vào nơi image đến, operating system, codec, storage format, và hơn nữa có thể tất cả ảnh hưởng vị trí của tọa độ của image cụ thể. Ví dụ, bạn có thể nghĩ bạn lấy mẫu các pixel từ một khuôn mặt trong phần tư đỉnh của một image trong khi bạn thực sự lấy mẫu một cái áo trong phần tư đáy. Tốt nhất là kiểm tra system lần đầu tiên bởi vẽ nơi bạn nghĩ bạn đang vận hành trên một image patch. dataOrder có thể là một trong IPL_DATA_ORDER_PIXEL hay IPL_DATA_ORDER_PLANE.* Giá trị này nhận biết có hay không data nên được gói bằng cái multiple channels sau khi cái khác cho mỗi pixel (interleaved, trường hợp bình thường), hay hơn là tất cả channel được bó thành các image plane với các plane đặt một cái sau khi cái khác. Parameter widthStep chứa số các byte giữa các điểm trong cùng cột và các hàng liên tiếp (tương tự với “step” parameter của CvMat được thảo luận trước). Variable width không đủ để tính khoảng cách vì mỗi hàng có thể được gióng với một số nào đó các byte để có được xử lý nhanh hơn của image; do đó có thể có vài kẽ hở giữa kết thúc của hàng i và bắt đầu hàng (i + 1). Parameter imageData chứa một pointer đến hàng đầu tiên của image data. Nếu có vài plane riêng trong image (như khi dataOrder = IPL_DATA_ORDER_PLANE) thì chúng được đặt liên tiếp như các image riêng với các hàng height*nChannels theo tổng, nhưng thông thường chúng được interleaved sao cho số hàng bằng với height và với mỗi hàng chứa các interleaved channel theo thứ tự. Cuối cùng có region of interest (ROI) thực tế và quan trọng, mà thực sự là một instance của một IPL/IPP structure khác, IplROI. Một IplROI chứa một xOffset, a yOffset, height, width, và coi, nơi COI viết tắt cho channel of interest.* Ý tưởng phía sau ROI là, một khi nó được đặt, các function mà thông thường làm việc trên toàn bộ image sẽ thay vào đó hoạt động chỉ trên tập con của image nhận diện bởi ROI. Tất cả OpenCV functions sẽ dùng ROI nếu được đặt. Nếu COI được đặt thành một value khác không thì vài operator sẽ làm việc chỉ trên channel được chỉ định. Không may, nhiều OpenCV functions bỏ qua parameter này. Accessing Image Data Khi làm việc với image data ta thường cần làm sao cho nhanh chóng và hiệu quả. Điều này gợi ý rằng ta không nên tự khất phụ việc ở trên của gọi các accessor function như cvSet*D hay tương đương của chúng. Thật ta, ta thích truy cập data bên trong của image theo hầu hết cách trực tiếp như có thể. Bằng kiến thức về bên trong IplImage structure, ta bây giờ có thể hiểu cách tốt nhất để làm điều này. Ngay cả qua các well-optimized routines trong OpenCV mà hoàn thành nhiều trong các nhiệm vụ ta cần thực hiện trên image, sẽ luôn luôn có các nhiệm vụ mà không có routine đóng gói sẵn trong library. Xem xét trường hợp của một three-channel HSV [Smith78] image mà trong đó ta muốn set saturation và value thành 255 (các value cực đại cho một 8-bit image) while để hue không biến đổi. Ta có thể là tốt nhất điều này bởi tự handling các pointers vào image, như ta làm với các matrix trong Example 3-9. Tuy nhiên, có vài khác biệt phụ mà nảy sinh từ khác biệt giữa IplImage và CvMat structures. Example 3-11 cho thấy cách nhanh nhất. Example 3-11. Maxing out (saturating) only the “S” và “V” parts of an HSV image 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; ptr[3*x+2] = 255; } } } Ta đơn giản tính pointer ptr trực tiếp như đầu của hàng y liên qua. Từ đó, ta de-reference saturation và value của cột x. Vì đây là threechannel image, vị trí channel c trong column x là 3*x+c. Một khác biệt quan trọng giữa trường hợp IplImage và the trường hợp CvMat là hành xử của imageData, so với element data của CvMat. Data element của CvMat là hợp nhất, do đó bạn phải nhận biết pointer type mà bạn muốn dùng. imageData pointer là byte pointer (uchar*). Ta hiện đã biết rằng data được trỏ đến không nhất thiết type uchar, mà có nghĩa rằng—khi làm đại số pointer trên images—bạn có thể đơn giản thêm widthStep (cũng được đo theo byte) không lo về data type thực đến sau khi phép cộng, khi bạn cast pointer kết quả thành data type bạn cần. Để recap: khi làm việc với các matrix, bạn phải scale down offset vì the data pointer có thể là nonbyte type; khi làm việc với images, bạn có thể dùng offset “as is” vì data pointer luôn luôn là byte type, do đó bạn có thể chỉ cast toàn bộ thứ khi bạn sẵn sàng dùng nó. More on ROI và widthStep ROI và widthStep có quan trọng thực hành lớn, vì trong nhiều trường hợp chúng tăng tốc các tác vụ computer vision bởi cho phép code xử lý chỉ vùng nhỏ của image. Hỗ trợ cho ROI và widthStep là phổ biến trong OpenCV:* Mọi function cho phép hoạt động được giới hạn trên một vùng phụ. Để bật ROI bật hay tắt, dùng cvSetImageROI() và cvResetImageROI() functions. Cho trước một vùng phụ chữ nhật mong muốn theo dạng của CvRect, bạn có thể chuyển image pointer và chữ nhật đến cvSetImageROI() để “bật” ROI; “tắt” ROI bởi chuyển image pointer đến cvResetImageROI(). void cvSetImageROI( IplImage* image, CvRect rect ); void cvResetImageROI( IplImage* image ); Để thấy cách ROI được dùng, hãy giả sử ta muốn load một image và modify vài region của image đó. Code trong Example 3-12 đọc một image và sau đó sets x, y, width, và height của ROI mong muốn và cuối cùng một integer value thêm vào để tăng ROI region. Program sau đó đặt ROI dùng thuận lợi của inline cvRect() constructor. Điều quan trọng là release ROI bằng cvResetImageROI(), ngược lại display sẽ quan sát ROI và display nghiêm túc chỉ ROI region. Example 3-12. dùng ImageROI to increment tất cả of the pixels in a region // roi_add <image> <x> <y> <width> <height> <add> #include <cv.h> #include <highgui.h> int main(int argc, char** argv) { IplImage* src; if( argc == 7 && ((src=cvLoadImage(argv[1],1)) != 0 )) { int x = atoi(argv[2]); int y = atoi(argv[3]); int width= atoi(argv[4]); int height = atoi(argv[5]); int add = atoi(argv[6]); cvSetImageROI(src, cvRect(x,y,width,height)); cvAddS(src, cvScalar(add),src); cvResetImageROI(src); cvNamedWindow( “Roi_Add”, 1 ); cvShowImage( “Roi_Add”, src ); cvWaitKey(); } return 0; } Hình 3-3 cho thấy kết quả của thêm 150 vào blue channel của image của con mèo với một ROI có tâm trên mặt nó, dùng code từ Example 3-12. Hình 3-3. Kết quả cộng 150 và mặt ROI của một con mèo Ta có thể có được cùng hiệu ứng bởi dùng thông minh widthStep. Để làm điều này, ta tạo một image header khác và đặt width và height bằng interest_rect width và height. Ta cũng cần set image origin (upper left hay lower left) để cùng với interest_img. Tiếp theo ta set widthStep của subimage thành widthStep của interest_img lớn hơn; cách này, bước bởi các hàng theo các bước subimage bạn đặt thích hợp ở bắt đầu của dòng tiếp theo của subregion bằng với image lớn hơn. Ta cuối cùng set subimage imageData pointer thành bắt đầu của subregion mong muốn, như được thấy trong Example 3-13. Example 3-13. dùng alternate widthStep method to increment tất cả of the pixels of interest_img by 1 // Assuming IplImage *interest_img; and // CvRect interest_rect; // dùng widthStep to get a region of interest // // (Alternate method) // IplImage *sub_img = cvCreateImageHeader( cvSize( interest_rect.width, interest_rect.height ), interest_img->depth, interest_img->nChannels ); sub_img->origin = interest_img->origin; sub_img->widthStep = interest_img->widthStep; sub_img->imageData = interest_img->imageData + interest_rect.y * interest_img->widthStep + interest_rect.x * interest_img->nChannels; cvAddS( sub_img, cvScalar(1), sub_img ); cvReleaseImageHeader(&sub_img); Do đó, vì sao bạn nên muốn dùng mẹo widthStep khi setting và resetting ROI dường như là qua thuận lợi? Nguyên nhân là có những lần khi bạn muốn set và chẳng hạn giữ nhiều subregions của một image tích cực trong khi xử lý, but ROI có thể chỉ được làm nối tiếp và phải được đặt và reset không đổi. Cuối cùng, một lời nên được nói ở đây về các mask. cvAddS() function dùng trong các code example cho phép dùng của một argument thứ tư mà mặc định là NULL: const CvArr*mask=NULL. Đây là một 8-bit single-channel array mà cho phép bạn hạn chế xử lý cho một vùng mặt nạ hình dáng bất kỳ nhận dện bởi các nonzero pixel trong mặt nạ. Nếu ROI được đặt cùng bới mask, xử lý sẽ được giới hạn với giao của ROI và mask. Các Mask có thể được dùng chỉ trong các functions mà chỉ định dùng chúng. Matrix và Image Operators Table 3-3 kê một lượng các routine để thao tác matrix, hầu hết mà làm việc tốt cho images. Chúng làm tất cả những việc “bình thường”, chẳng hạn diagonalizing hay transposing một matrix, cũng như vài tác vụ phức tạp hơn, chẳng hạn tính thống kê image. Table 3-3. Basic matrix và image operators Function Mô tả cvAbs Trị tuyệt đối tất cả elements trong array cvAbsDiff Trị tuyệt đối các khác biệt giữa hai arrays cvAbsDiffS Trị tuyệt đối các khác biệt giữa array và scalar cvAdd Cộng elementwise của hai array cvAddS Elementwise addition of an array và a scalar cvAddWeighted Elementwise weighted addition of hai arrays (alpha blending) cvAvg Average value of tất cả elements in an array cvAvgSdv Absolute value và standard deviation of tất cả elements in an array cvCalcCovarMatrix Compute covariance of a set of n-dimensional vectors cvCmp Apply selected comparison operator to tất cả elements in hai arrays cvCmpS Apply selected comparison operator to an array relative to a scalar cvConvertScale Convert array type with optional rescaling of the value cvConvertScaleAbs Convert array type sau khi absolute value with optional rescaling cvCopy Copy elements of one array to another cvCountNonZero Count nonzero elements in an array cvCrossProduct Compute cross product of hai three-dimensional vectors cvCvtColor Convert channels of an array from one color space to another cvDet Compute determinant of a square matrix cvDiv Elementwise division of one array by another cvDotProduct Compute dot product of hai vectors cvEigenVV Compute eigenvalues và eigenvectors of a square matrix cvFlip cvGEMM cvGetCol cvGetCols cvGetDiag cvGetDims cvGetDimSize cvGetRow cvGetRows cvGetSize cvGetSubRect cvInRange cvInRangeS cvInvert cvMahalonobis cvMax cvMaxS cvMerge cvMin cvMinS cvMinMaxLoc cvMul cvNot cvNorm cvNormalize cvOr cvOrS cvReduce cvRepeat cvSet cvSetZero cvSetIdentity cvSolve cvSplit cvSub cvSubS cvSubRS cvSum cvSVD cvSVBkSb cvTrace cvTranspose cvXor cvXorS cvZero Flip an array about a selected axis Generalized matrix multiplication Copy elements from column slice of an array Copy elements from multiple adjacent columns of an array Copy elements from an array diagonal Return the number of dimensions of an array Return the sizes of tất cả dimensions of an array Copy elements from row slice of an array Copy elements from multiple adjacent rows of an array Get size of a two-dimensional array và return as CvSize Copy elements from subregion of an array Test if elements of an array are with in values of hai other arrays Test if elements of an array are in range giữa hai scalars Invert a square matrix Compute Mahalonobis khoảng cách giữa hai vectors Elementwise max operation on hai arrays Elementwise max operation giữa an array và a scalar Merge vài single-channel images into one multichannel image Elementwise min operation on hai arrays Elementwise min operation giữa an array và a scalar tìm thấy minimum và maximum values in an array Elementwise multiplication of hai arrays Bitwise inversion of every element of an array Compute normalized correlations giữa hai arrays Normalize elements in an array to some value Elementwise bit-level hay of hai arrays Elementwise bit-level hay of an array và a scalar Reduce a two-dimensional array to a vector by a given operation Tile the contents of one array into another Set tất cả elements of an array to a given value Set tất cả elements of an array to 0 Set tất cả elements of an array to 1 for the diagonal và 0 otherwise Solve a system of linear equations Split a multichannel array into multiple single-channel arrays Elementwise subtraction of one array from another Elementwise subtraction of a scalar from an array Elementwise subtraction of an array from a scalar Sum tất cả elements of an array Compute singular value decomposition of a twodimensional array Compute singular value back-substitution Compute the trace of an array Transpose tất cả elements of an array across the diagonal Elementwise bit-level XOR giữa hai arrays Elementwise bit-level XOR giữa an array và a scalar Set tất cả elements of an array to 0 cvAbs, cvAbsDiff, và cvAbsDiffS void cvAbs( const CvArr* src, const dst ); void cvAbsDiff( const CvArr* src1, const CvArr* src2, const dst ); void cvAbsDiffS( const CvArr* src, CvScalar value, const dst ); Các function này tính trị tuyệt đối của array hay khác biệt giữa array và vài reference. cvAbs() function đơn giản tính trị tuyệt đối của các element trong src và viết kết quả vào dst; cvAbsDiff() đầu tiên trừ src2 với src1 và sau đó viết trị tuyệt đối hiệu vào dst. Lưu ý rằng cvAbsDiffS() thực sự là giống như cvAbsDiff() ngoại trừ giá trị được trừ với tất cả element của src là giá trị scalar hằng. cvAdd, cvAddS, cvAddWeighted, và alpha blending void cvAdd( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask = NULL ); void cvAddS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask = NULL ); void cvAddWeighted( const CvArr* src1, double alpha, const CvArr* src2, double beta, double gamma, CvArr* dst ); cvAdd() kà function cộng đơn giản: nó cộng tất cả elements trong src1 với các element tương ứng trong src2 và đựt các kết quả vào dst. Nếu mask không đặt thành NULL, thì bất kỳ element của dst mà tương ứng với zero element của mask vẫn không đổi bởi tác vụ này. Function tương đối quen là cvAddS() là điều tương tự ngoại trừ scalar value hằng được thêm vào mỗi element của src. Function cvAddWeighted() giống với cvAdd() ngoại trừ kết quả viết vào dst được tính tỉ lệ theo công thức sau: Function này có thể được dùng để thực hiện alpha blending [Smith79; Porter84]; mà, nó có thể được dùng trộn một image với cái khác. Dạng của function này là: void cvAddWeighted( const CvArr* src1, double alpha, const CvArr* src2, double beta, double gamma, CvArr* dst ); Trong cvAddWeighted() ta có hai source image, src1 và src2. những images này có thể là bất kỳ pixel type và cả hai cùng type. They có thể cũng là một hay ba channels (grayscale hay color), lần nữa chúng phải giống nhau. Image kết quả, dst, phải cũng có cùng pixel type như src1 và src2. những images này có thể khác nhau size, nhưng các ROI của chúng phải thống nhất theo size hay OpenCV sẽ phát ra một error. Parameter alpha là blending strength của src1, và beta là blending strength của src2. Phương trình alpha blending là: Bạn có thể chuyển thành phương trình alpha blend chuẩn bởi chọn α giữa 0 và 1, setting β = 1 – α, và setting γ thành 0; điều này tạo ra: Tuy nhiên, cvAddWeighted() cho ta sự linh hoạt hơn —cả trong cách ta đặt nặng các blended image và trong parameter phụ γ, mà cho phép một additive offset vào image đích. Từ dạng chung, bạn sẽ có thể muốn giữ alpha và beta ở không nhỏ hơn 0 và tổng của chúng không lớn hơn 1; gamma có thể được đặt phụ thuộc vào trung bình hay image value cực đại để tỉ lệ các pixel. Một program cho thấy việc dùng alpha blending được thấy trong Example 3-14. Example 3-14. Program hoàn chỉnh để alpha blend ROI bắt đầu ở (0,0) trong src2 với ROI bắt đầu ở (x,y) trong src1 // alphablend <imageA> <image B> <x> <y> <width> <height> // <alpha> <beta> #include <cv.h> #include <highgui.h> int main(int argc, char** argv) { IplImage *src1, *src2; if( argc == 9 && ((src1=cvLoadImage(argv[1],1)) != 0 )&&((src2=cvLoadImage(argv[2],1)) != 0 )) { int x = atoi(argv[3]); int y = atoi(argv[4]); int width= atoi(argv[5]); int height = atoi(argv[6]); double alpha = (double)atof(argv[7]); double beta = (double)atof(argv[8]); cvSetImageROI(src1, cvRect(x,y,width,height)); cvSetImageROI(src2, cvRect(0,0,width,height)); cvAddWeighted(src1, alpha, src2, beta,0.0,src1); cvResetImageROI(src1); cvNamedWindow( “Alpha_blend”, 1 ); cvShowImage( “Alpha_blend”, src1 ); cvWaitKey(); } return 0; } Code trong Example 3-14 lấy hai source images: cái chính (src1) và cái để blend (src2). Nó đọc một rectangle ROI cho src1 và áp dụng một ROI cùng size cho src2, lần này được cấp ở gốc. Nó đọc alpha và beta levels nhưng đặt gamma thành 0. Alpha blending được áp dụng dùng cvAddWeighted(), và các kết quả được đặt vào src1 và hiển thị. Example output được thấy trong Hình 3-4, nơi mặt em bé được blend vào mặt và người của mèo. Lưu ý rằng code lấy cùng ROI như trong ví dụ cộng ROI trong Hình 3-3. Lần này ta dùng ROI như vùng blending đích. cvAnd và cvAndS void cvAnd( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask = NULL ); void cvAndS( const CvArr* src1, CvScalar value, CvArr* dst, const CvArr* mask = NULL ); Hai functions này tính bitwise và hoạt động trên array src1. Trong trường hợp của cvAnd(), mỗi element của dst được tính với bitwise và với hai element tương ứng của src1 và src2. Trong trường hợp của cvAndS(), bitwise và được tính với scalar value không đổi. Như luôn luôn, nếu mask là không NULL thì chỉ các element của dst tương ứng với các nonzero entry trong mask được tính. Dù tất cả data types được hỗ trợ, src1 và src2 phải có cùng data type cho cvAnd(). Nếu các elements là floating-point type, thì biểu diễn bitwise của floating-point number đó được dùng. Hình 3-4. Mặt em bé được alpha blended vào mặt con mèo cvAvg CvScalar cvAvg( const CvArr* arr, const CvArr* mask = NULL ); cvAvg() tính giá trị trung bình của các pixel trong arr. Nếu mask là khác NULL thì trung bình sẽ được tính trên các pixel đó mà cho giá trị tường ứng của mask là khác không. Function này has the bây giờ deprecated alias cvMean(). cvAvgSdv cvAvgSdv( const CvArr* arr, CvScalar* mean, CvScalar* std_dev, const CvArr* mask = NULL ); Function này giống cvAvg(), nhưng thêm vào trung bình nó cũng tính vi phân chuẩn của các pixel. Function này bây giờ đã mất giá so với cvMean_StdDev(). cvCalcCovarMatrix void cvAdd( const CvArr** vects, int count, CvArr* cov_mat, CvArr* avg, int flags ); Cho trước bất kỳ số vector, cvCalcCovarMatrix() sẽ tính trung bình và covariance matrix cho xấp xỉ Gaussian cho phân phối của các điểm này. Điều này có thể được dùng theo nhiều cách, dĩ nhiên, và OpenCV có vài flag phụ mà that sẽ giúp cụ thể theo ngữ cảnh (xem Table 3-4). Những flags này có thể được kết hợp bởi việc dùng chuẩn của Boolean hay operator. Table 3-4. Các thành phần có thể của flags argument cho cvCalcCovarMatrix() Flag trong flags ý nghĩa argument CV_COVAR_NORMAL Tính mean và covariance PCA “scrambled” covariance nhanh Dùng avg như input thay vì tính nó Rescale output covariance matrix Trong tất cả trường hợp, các vectors được cung cấp theo các vect như một array của các CV_COVAR_SCRAMBLED CV_COVAR_USE_AVERAGE CV_COVAR_SCALE OpenCV array (chẳng hạn một pointer đến một list các pointer đến các array), với argument count nhận biết bao nhiêu array đang được cung cấp. Các kết quả sẽ được đặt trong cov_mat trong tất cả trường hợp, nhưng ý nghĩa chính xác của avg phụ thuộc vào các giá trị flag (xem Table 3-4). Các flags CV_COVAR_NORMAL và CV_COVAR_SCRAMBLED loại trừ nhau; bạn nên dùng cái này hay cái kia nhưng không cả hai. Trong trường hợp của CV_COVAR_NORMAL, function sẽ đơn giản tính mean và covariance các điểm được cung cấp. –v n được định nghĩa như phần tử thứ n của average vector v –. Covariance matrix kết quả là một nby-n matrix. Factor z là một scale factor tùy chọn; nó sẽ được đặt thành 1 trừ phi CV_COVAR_SCALE flag được dùng. Trong trường hợp của CV_COVAR_SCRAMBLED, cvCalcCovarMatrix() sẽ tính như sau: Matrix này không là covariance matrix bình thường (lưu ý vị trí của transpose operator). Matrix này được tính từ cùng các m vector chiều dài n, nhưng scrambled covariance matrix kết quả là một m-by-m matrix. Matrix này được dùng trong vài thuật toán đặc biệt chẳng hạn PCA nhanh các vector rất lớn (như trong kỹ thuật eigenfaces cho face recognition). Flag CV_COVAR_USE_AVG được dùng khi mean của các input vector hiện đã biết. Trong trường hợp này, argument avg được dùng như input hơn một output, mà giảm thời gian tính toán. Cuối cùng, flag CV_COVAR_SCALE được dùng để áp uniform scale cho covariance matrix được tính. Đây là factor z trong các phương trình trước. Khi được dùng kết hợp với CV_COVAR_NORMAL flag, scale factor được áp sẽ là 1.0/m (hay, tương đương, 1.0/ count). Nếu thay vì CV_COVAR_SCRAMBLED được dùng, thì giá trị z sẽ là 1.0/n (nghịch đảo chiều dài các vector). Các input và output array cho cvCalcCovarMatrix() nên tất cả là cùng floating-point type. Size của matrix cov_mat kết quả sẽ là một trong n-by-n hay m-by-m phụ thuộc vào có hay không covariance chuẩn hay scrambled đang được tính. nên được lưu ý rằng các “vectors” input trong vects không thực sự phải là một chiều; chúng có thể là các two-dimensional object (chẳng hạn các image). cvCmp và cvCmpS void cvCmp( const CvArr* src1, const CvArr* src2, CvArr* dst, int cmp_op ); void cvCmpS( const CvArr* src, double value, CvArr* dst, int cmp_op ); Cả hai function này làm các so sánh, một trong giữa pixels tương ứng trong hai images hay giữa pixels trong một image và một scalar value không đổi. Cả cvCmp() và cvCmpS() lấy argument cuối của chúng là một comparison operator, mà có thể là bất kỳ types kê trong Table 3-5. Table 3-5. Các giá trị của cmp_op được dùng bởi cvCmp() và cvCmpS() và comparison operation kết quả được thực hiện Value of cmp_op Comparison CV_CMP_EQ CV_CMP_GT (src1i == src2i) (src1i > src2i) CV_CMP_GE CV_CMP_LT CV_CMP_LE CV_CMP_NE (src1i >= src2i) (src1i < src2i) (src1i <= src2i) (src1i != src2i) Tất cả so sánh được kê được làm với cùng các function; bạn chỉ chuyển vào argument thích hợp để nhận diện cái bạn muốn làm. Những functions cụ thể này làm việc chỉ trên các single-channel image. Các function so sánh này hữai ích trong các application nơi bạn làm vài phiên bản của background subtraction và muốn mask các kết quả (chẳng hạn quan sát video stream từ một security camera) mà chỉ những thông tin mới được đẩy ra khỏi image. cvConvertScale void cvConvertScale( const CvArr* src, CvArr* dst, double scale = 1.0, double shift= 0.0 ); cvConvertScale() function thực sự là vài function được gom thành một cái; ms sẽ thực hiện bất kỳ trong vài functions hay, nếu cần, tất cả chúng cùng nhau. Function đầu tiên là chuyển data type trong source image thành data type của destination image. Ví dụ, nếu ta have một 8-bit RGB grayscale image và mà muốn chuyển thành 16-bit signed image, ta có thể làm bởi gọi cvConvertScale(). Function thứ hai của cvConvertScale() là thực hiện biến đổi tuyến tính trên image data. Sau khi chuyển thành data type mới, mỗi pixel value sẽ được nhận bởi value scale và sau đó cộng nó vào value shift. Điều quan trọng phải nhớ, ngay cả qua “Convert” đi trước “Scale” trong function name, thứ tự thực mà trong những operations này được thực hiện là ngược lại. Đặc biệt, việc nhân bởi scale và cộng shift xảy ra trước khi chuyển type được làm. Khi bạn đơn giản chuyển các value mặc định (scale = 1.0 và shift= 0.0), bạn không cần có các sợ hãi hiệu suất; OpenCV đủ thông minh để nhận biết trường hợp này và không lãng phí processor time cho các tác vụ vô ích. Để rõ ràng (nếu bạn nghĩ nó cộng tùy ý), OpenCV cũng cung cấp macro cvConvert(), mà là giống như cvConvertScale() nhưng được dùng theo thói quen khi các tham số scale và shift sẽ được để ở giá trị mặc định của chúng. cvConvertScale() sẽ làm việc trên tất cả data types và bất kỳ số channel, nhưng số channel trong source và destination images phải giống nhau. (nếu bạn muốn chuyển từ color thành grayscale hay ngược lại, xem cvCvtColor(), mà đến ngay sau.) cvConvertScaleAbs void cvConvertScaleAbs( const CvArr* src, CvArr* dst, double scale = 1.0, double shift= 0.0 ); cvConvertScaleAbs() thực sự là đúng cho cvConvertScale() ngoại trừ rằng dst image chứa giá trị tuyệt đối của data kết quả. Đặc biệt, cvConvertScaleAbs() các scales và shifts đầu tiên, sau đó tính giá trị tuyệt đối, và cuối cùng thực hiện chuyển datatype. cvCopy void cvCopy( const CvArr* src, CvArr* dst, const CvArr* mask = NULL ); Đây là cách bạn copy một image thành cái khác. cvCopy() function mong cả hai array phải có cùng kiểu, cùng size, và cùng số các dimension. Bạn có thể dùng nó để copy các array thưa, nhưng cho điều này việc dùng của mask không được hỗ trợ. Cho các arrays không thưa và images, hiệu ứng của mask (nếu khác NULL) chỉ là các pixels trong dst mà tương ứng với các entry khác không trong mask sẽ được thay đổi. cvCountNonZero int cvCountNonZero( const CvArr* arr ); cvCountNonZero() trả về số các pixel khác không trong array arr. cvCrossProduct void cvCrossProduct( const CvArr* src1, const CvArr* src2, CvArr* dst ); Function này tính vector cross product [Lagrange1773] của hai three-dimensional vectors. Nó không quan tâm các vectors ở dạng hàng hay cột (một ít phản xạ tiết lộ rằng, cho các singlechannel objects, hai cái này thực sự là giống nhau bên trong). Cả src1 và src2 nên là các singlechannel arrays, và dst sẽ là single-channel và chiều dài chính xác là 3.Tất cả ba arrays sẽ là của cùng data type. cvCvtColor void cvCvtColor( const CvArr* src, CvArr* dst, int code ); Các function trước là để chuyển từ một data type thành cái khác, và chúng muốn số các channel là giống nhau trong cả các source và destination image. Function hoàn chỉnh là cvCvtColor(), mà chuyển từ một color space (number of channels) thành một cái khác [Wharton71] trong khi muốn data type là giống nhau. Tác vụ chuyển đổi chính xác được làm được chỉ định bởi argument code, mà các giá trị có thể được kê trong Table 3-6.* Table 3-6. Conversions available by means of cvCvtColor() Conversion code Meaning CV_BGR2RGB Chuyển giữa RGB và BGR color spaces (có CV_RGB2BGR hay không có alpha channel) CV_RGBA2BGRA CV_BGRA2RGBA CV_RGB2RGBA CV_BGR2BGRA CV_RGBA2RGB CV_BGRA2BGR CV_RGB2BGRA CV_RGBA2BGR CV_BGRA2RGB CV_BGR2RGBA CV_RGB2GRAY CV_BGR2GRAY CV_GRAY2RGB CV_GRAY2BGR CV_RGBA2GRAY CV_BGRA2GRAY CV_GRAY2RGBA CV_GRAY2BGRA CV_RGB2BGR565 CV_BGR2BGR565 CV_BGR5652RGB CV_BGR5652BGR CV_RGBA2BGR565 CV_BGRA2BGR565 CV_BGR5652RGBA CV_BGR5652BGRA Thêm alpha channel vào RGB hay BGR image CV_GRAY2BGR565 CV_BGR5652GRAY CV_RGB2BGR555 CV_BGR2BGR555 CV_BGR5552RGB CV_BGR5552BGR CV_RGBA2BGR555 CV_BGRA2BGR555 CV_BGR5552RGBA CV_BGR5552BGRA Convert grayscale to BGR565 color representation hay vice versa (16-bit images) Convert from RGB hay BGR color space to BGR555 color representation with optional addition hay removal of alpha channel (16-bit images) Xóa alpha channel khỏi RGB hay BGR image Chuyển RGB thành BGR color spaces trong khi thêm hay xóa alpha channel Chuyển RGB hay BGR color spaces thành grayscale Chuyển grayscale thành RGB hay BGR color spaces (tùy chọn xóa alpha channel trong xử lý) Convert grayscale to RGB hay BGR color spaces và add alpha channel Convert from RGB hay BGR color space to BGR565 color representation with optional addition hay removal of alpha channel (16-bit images) CV_GRAY2BGR555 CV_BGR5552GRAY CV_RGB2XYZ CV_BGR2XYZ CV_XYZ2RGB CV_XYZ2BGR CV_RGB2YCrCb CV_BGR2YCrCb CV_YCrCb2RGB CV_YCrCb2BGR CV_RGB2HSV CV_BGR2HSV CV_HSV2RGB CV_HSV2BGR CV_RGB2HLS CV_BGR2HLS CV_HLS2RGB CV_HLS2BGR CV_RGB2Lab CV_BGR2Lab CV_Lab2RGB CV_Lab2BGR CV_RGB2Luv CV_BGR2Luv CV_Luv2RGB CV_Luv2BGR CV_BayerBG2RGB CV_BayerGB2RGB CV_BayerRG2RGB CV_BayerGR2RGB CV_BayerBG2BGR CV_BayerGB2BGR CV_BayerRG2BGR CV_BayerGR2BGR Convert grayscale to BGR555 color representation hay vice versa (16-bit images) Convert RGB hay BGR image to CIE XYZ representation hay vice versa (Rec 709 with D65 white point) Convert RGB hay BGR image to luma-chroma (aka YCC) color representation Convert RGB hay BGR image to HSV (hue saturation value) color representation or vice versa Convert RGB hay BGR image to HLS (hue lightness saturation) color representation or vice versa Convert RGB hay BGR image to CIE Lab color representation hay vice versa Convert RGB hay BGR image to CIE Luv color representation Convert from Bayer pattern (single-channel) to RGB hay BGR image Các chi tiết của nhiều trong những chuyển đổi này là quan trọng, và ta sẽ không đi vào chủ đề của biểu diễn Bayer của các CIE color spaces ở đây. Cho các mục đích của ta, là đủ để lưu ý rằng OpenCV chứa các tool để chuyển thành và từ những color space khác nhau, mà là quan trọng với các lớp khác nhau người dùng. Chuyển đổi color-space tất cả dùng các chuyển đổi: 8-bit images ở trong dải 0–255, 16-bit images ở trong dải 0–65536, và các số floating-point ở trong dải 0.0–1.0. Khi grayscale images được chuyển thành color images, tất cả các thành phần của image kết quả được lấy là bằng; nhưng cho biến đổi ngược (chẳng hạn RGB hay BGR thành grayscale), gray value được tính bởi dùng công thức trọng số cảm giác sau: Y(0.299)R(0.587)G(0.114)B Trong trường hợp của biểu diễn HSV hay HLS, hue thông thường được biểu diễn như value từ 0 đến 360.* Điều này có thể gây ra rắc rối trong các biểu diễn 8-bit và do đó, khi chuyển thành HSV, hue được chia bởi 2 khi output image là một 8-bit image. cvDet double cvDet( const CvArr* mat ); cvDet() tính determinant (Det) crr một array vuông. Array có thể là bất kỳ data type, nhưng nó phải là single-channel. Nếu matrix là nhỏ thì determinant trực tiếp được tính bởi công thức chuẩn. Cho các các matrix lớn, điều này đặc biệt không hiệu quả và do đó determinant được tính bởi Gaussian elimination. Nếu đáng để biết bạn hiện biết rằng một matrix là đối xứng và có một determinant dương, bạn có thể cũng dùng mẹo để giải qua singular value decomposition (SVD). Cho nhiều thông tin hơn xem Phần “cvSVD” theo sau, nhưng mẹo là đặt cả U và V thành NULL và sau đó lấy các product của matrix W để lấy determinant. cvDiv void cvDiv( const CvArr* src1, const CvArr* src2, CvArr* dst, double scale = 1 ); cvDiv() là function chia đơn giản; nó chia tất cả elements trong src1 bởi các elements tương ứng trong src2 và đặt các kết quả vào dst. Nếu mask là khác NULL, thì bất kỳ element của dst mà tương ứng với một zero element của mask không được thay đổi bởi operation này. Nếu bạn chỉ muốn nghịch đảo tất cả elements trong một array, bạn có thể chuyển NULL ở vị trí của src1; routine sẽ làm việc với điều này như một array đủ của cái số một. cvDotProduct double cvDotProduct( const CvArr* src1, const CvArr* src2 ); Function này tính vector dot product [Lagrange1773] của hai N-dimensional vectors.* Như với cross product (và cho cùng nguyên nhân), nó không gì nếu các vector ở dạng hàng hay cột. Cả src1 và src2 nên là các single-channel arrays, và cả hai array nên là cùng kiểu data. cvEigenVV double cvEigenVV( CvArr* mat, CvArr* evects, CvArr* evals, double eps = 0 ); Cho trước một matrix đối xứng mat, cvEigenVV() sẽ tính eigenvectors và eigenvalues tương ứng của matrix đó. Điều này được làm dùng Jacobi’s method [Bronshtein97], do đó nó hiệu quả cho các matrix nhỏ hơn.† Jacobi’s method đòi hỏi một stopping parameter, mà là kích thước cực đại của các off-diagonal elements trong matrix cuối.‡ Optional argument eps đặt giá trị kết thúc. Trong tiến trình của tính toán, matrix được cung cấp mat được dùng để tính, do đó các value của nó sẽ bị thay đổi bởi function. Khi function returns, bạn sẽ tìm thấy các eigenvector của bạn trong evects ở dạng của các hàng tiếp theo. Các giá trị eigen tương ứng được lưu trong evals. Thứ tự các eigenvector sẽ luôn luôn là ngược theo biên độ của các eigenvalues tương ứng. cvEigenVV() function đòi hỏi tất cả ba arrays cho floating-point type. Như với cvDet() (mô tả trước đây), nếu matrix đang yêu cầu được biết là đối xứng và positive definite § thì tốt hơn là dùng SVD để tìm eigenvalues và eigenvectors của mat. cvFlip void cvFlip( const CvArr* src, CvArr* dst = NULL, int flip_mode = 0 ); Function này lập một image quanh x-axis, the y-axis, hay cả hai. Về cụ thể, nếu argument flip_mode được đặt thành 0 thì image sẽ được lập quanh x-axis. Nếu flip_mode được đặt tành một giá trị dương (chẳng hạn +1) image sẽ được lập quanh yaxis, và nếu đặt thành âm (chẳng hạn – 1) image sẽ được lật cả hai trục. Khi xử lý video trên các Win32 systems, bạn sẽ tự tìm thấy việc dùng function này thường để chuyển giữa các image format vơiis gốc của chúng ở upper-left và lower-left của image. cvGEMM double cvGEMM( const CvArr* src1, const CvArr* src2, double alpha, const CvArr* src3, double beta, CvArr* dst, int tABC = 0 ); Generalized matrix multiplication (GEMM) trong OpenCV được thực hiện bởi cvGEMM(), mà thực hiện nhân matrix, nhân bởi một transpose, scaled multiplication, et cetera. Trong dạng chuẩn hầu hết của nó, cvGEMM() tính như sau: Trong đó A, B, và C là (tương ứng) các matrix src1, src2, và src3, α và β là các hệ số, và op() là một transposition tùy chọn của matrix bao quanh. Argument src3 có thể được đặt thành NULL, mà trong trường hợp nó sẽ không được cộng. Các transpositions được khiển bởi optional argument tABC, mà có thể là 0 hay bất kỳ sự kết hợp (bởi các phương tiện của Boolean OR) của CV_GEMM_A_T, CV_GEMM_B_T, và CV_GEMM_C_T (với mỗi flag nhận diện một transposition của matrix tương ứng). Trong quá khứ xa OpenCV chứa các methods cvMatMul() và cvMatMulAdd(), nhưng những cái này thường quá lộn xộn với cvMul(), mà mà gì đó khác hoàn toàn (chẳng hạn nhân phần tử với phần tử của hai array). những functions này tiếp tục tồn tại như các macro cho các lời gọi đến cvGEMM(). Về cụ thể, ta có hai tương đương kê trong Table 3-7. Table 3-7. Macro aliases for common usages of cvGEMM() cvMatMul(A, B, D) cvMatMulAdd(A, B, C, D) cvGEMM(A, A, 1, NULL, 0, D, 0) cvGEMM(A, A, 1, C, 1, D, 0) Tất cả các matrix phải ở size thích hợp để nhân, và tất cả nên là kiểu floating-point. cvGEMM() function hỗ trợ các matrix two-channel, mà trong trường hợp nó sẽ làm hai channels như hai components của một single complex number. cvGetCol và cvGetCols CvMat* cvGetCol( const CvArr* arr, CvMat* submat, int col ); CvMat* cvGetCols( const CvArr* arr, CvMat* submat, int start_col, int end_col ); Function cvGetCol() được dùng để lâyys một column đơn ra khỏi một matrix và return nó như một vector (chẳng hạn như một matrix với chỉ một column). Trong trường hợp này matrix header submat sẽ được biến đổi để trỏ đến một column cụ thể trong arr. Điều quan trọng cần lưu ý rằng biến đổi header như thế không include allocation of memory hay copying của data. Nội dung của submat sẽ đơn giản được thay đổi sao cho nó nhận biết đúng column được chọn trong arr. Tất cả data types được hỗ trợ. cvGetCols() làm việc chính xác theo cùng cách, ngoại trừ rằng tất cả columns từ start_col đến end_col được chọn. Với cả hai function, return value là một pointer đến một header tương ứng với column hay column span được chỉ đinh cụ thể (chẳng hạn submat) được chọn bởi trình gọi. cvGetDiag CvMat* cvGetDiag( const CvArr* arr, CvMat* submat, int diag = 0 ); cvGetDiag() tương tưj với cvGetCol(); nó được dùng để lấy một single diagonal từ một matrix và return nó như một vector. Argument submat là một matrix header. Function cvGetDiag() sẽ điền các component của header này sao cho nó trỏ đến thông tin đúng trong arr. Lưu ý rằng kết quả của gọi cvGetDiag() mà là header bạn cung cấp là được cấu hình đúng để trỏ đênns diagonal data trong arr, nhưng data từ arr không được copy. Optional argument diag chỉ định diagonal mà được trỏ đến bởi submat. Nếu diag được đặt thành default value 0, diagonal chính sẽ được chọn. Nếu diag lớn hơn 0, thì diagonal bắt đầu ở (diag,0) sẽ được chọn; nếu diag nhỏ hơn 0, thì diagonal bắt đầu ở (0,-diag) sẽ được chọn thay. cvGetDiag() function không đòi hỏi matrix arr là vuông, nhưng array submat phải có chiều dài đúng cho size của input array. Returned value cuối là giống như value của submat chuyển vào khi the function được gọi. cvGetDims và cvGetDimSize int cvGetDims( const CvArr* arr, int* sizes=NULL ); int cvGetDimSize( const CvArr* arr, int index ); Nhớ rằn các arrays trong OpenCV có thể có chiều lớn hơn hai. Function cvGetDims() trả về số các chiều array của một array cụ thể và (optionally) các size của mỗi dimension này. Các size sẽ được báo cáo nếu các array size là không NULL. Nếu sizes được dùng, nó nên là một một pointer đến n integers, trong đó n là số các dimension. Nếu bạn không biết số dimension trước, bạn có thể cấp các sizes cho CV_MAX_DIM integers chỉ để an toàn. Function cvGetDimSize() trả về size của dimension đơn được chỉ định bởi index. Nếu array là một trong một matrix hay một image, số các dimension được return sẽ luôn luôn là hai.* Cho các các matrix và images, thứ tự của size trả về bởi cvGetDims() sẽ luôn luôn là số các hàng đầu tiên theo sau bởi số các cột. cvGetRow và cvGetRows CvMat* cvGetRow( const CvArr* arr, CvMat* submat, int row ); CvMat* cvGetRows( const CvArr* arr, CvMat* submat, int start_row, int end_row ); cvGetRow() lấy một single row ra khỏi một matrix và trả về nó như một vector (một matrix với chỉ một hàng). Như với cvGetRow(),matrix header submat sẽ được biến đổi thành trỏ đến một hàng cụ thể trong arr, và các biên đổi của header này không bao gồm việc cấp memory hay copying data; nội dung của submat sẽ đơn giản được thay đổi như nó nhận biết đúng cột được chọn trong arr. Tất cả data types được hỗ trợ. Function cvGetRows() làm việc chính xác giống cách, ngoại trừ rằng tất cả các hàng từ start_row đến end_row được chọn. Cới cả hai function, return value là một pointer đến một header tương ứng với hàng hay row span chỉ định cụ thể chọn bởi trình gọi. cvGetSize CvSize cvGetSize( const CvArr* arr ); Liên quan gần với cvGetDims(), cvGetSize() trả về size của một array. Khác biệt chính là cvGetSize() được thiết kế để được dùng trên các matrix và images, mà luôn luôn có dimension là hai. Size có thể sau được trả về theo dạng của một CvSize structure, mà phù hợp để dùng khi (ví dụ) xây dựng matrix hay image mới của cùng size. cvGetSubRect CvSize cvGetSubRect( const CvArr* arr, CvArr* submat, CvRect rect ); cvGetSubRect() là tương tự với cvGetColumns() hay cvGetRows() ngoại trừ rằng nó chọn vài hình chữ nhật con tùy ý trong array chỉ định bởi argument rect. Như với các routine khác mà chọn các phần phụ của các array, submat đơn giản là một header mà sẽ được điền bởi cvGetSubRect() theo một cách mà nó trỏ đúng đến submatrix mong muốn (chẳng hạn không có memory được cấp và không data được copy). cvInRange và cvInRangeS void cvInRange( const CvArr* src, const CvArr* lower, const CvArr* upper, CvArr* dst ); void cvInRangeS( const CvArr* src, CvScalar lower, CvScalar upper, CvArr* dst ); Hai functions này có thể được dùng để kiểm tra có các pixels trong một image rơi vào một dải được chỉ định cụ thể. Trong trường hợp của cvInRange(), mỗi pixel của src được so với value tương ứng trong các images lower và upper. Nếu value trong src là lớn hơn hay bằng value trong lower và cũng nhỏ hơn value trong upper, sau đó value tương ứng trong dst sẽ được đặt thành 0xff; ngược lại, value trong dst sẽ được đặt thành 0. Function cvInRangeS() làm việc chính xác cùng cách ngoại trừ rằng image src được so với các giá trị hằng (CvScalar) theo lower và upper. Cho cả hai function, image src có thể là bất kỳ type; nếu nó có nhiều channels thì mỗi channel sẽ được handle riêng. Lưu ý rằng dst phải là cùng size và số channels và cũng phải một 8-bit image. cvInvert double cvInvert( const CvArr* src, CvArr* dst, Int method = CV_LU ); cvInvert() nghịch đảo matrix trong src và đặt kết quả trong dst. Function này hỗ trợ vài methods của tính inverse matrix (xem Table 3-8), nhưng mặc định là Gaussian elimination. Return value phụ thuộc vào method được dùng. Table 3-8. Possible values of method argument to cvInvert() Value of method argument Meaning CV_LU Gaussian elimination (LU Decomposition) CV_SVD Singular value decomposition (SVD) CV_SVD_SYM SVD for symmetric các matrix Trong trường hợp của Gaussian elimination (method=CV_LU), determinant của src được trả về khi function hoàn thành. Nếu determinant là 0, thì nghịch đảo không thực sự được thực hiện và array dst đơn giản được đặt tất cả thành 0. Trong trường hợp của CV_SVD hay CV_SVD_SYM, return value là số điều kiện nghịch đảo cho matrix (ratio của eigenvalue nhỏ nhất đến lớn nhất). Nếu matrix src là singular, thì cvInvert() trong SVD mode sẽ thay vào tính pseudo-inverse. cvMahalonobis CvSize cvMahalonobis( const CvArr* vec1, const CvArr* vec2, CvArr* mat ); Khoảng cách Mahalonobis (Mahal) được định nghĩa như vector khoảng cách đo giữa một điểm và tâm của phân phối Gaussian ; nó được tính dùng inverse covariance mà phân phối đó như một metric. Xem Figure 3-5. Về trực giác, điều này tương tự với z-score trong thông kê cơ bản, trong đó khoảng cách từ tâm của phân phối được đo theo đơn vị thay đổi của phân phối đó. Khoảng cách Mahalonobis chỉ là tổng quát hóa đa biến của cùng ý tưởng. cvMahalonobis() tính giá trị này: rMahalonobis ( x )T ( x ) 1 Vector vec1 được cộng trước để là điểm x, và vector vec2 được lấy để là hiệu dụng của phân phối.* Matrix mat là inverse covariance. Trong thực tế, covariance matrix này sẽ thường được tính bằng cvCalcCovar Matrix() (được mô tả trước đây) và sau đó nghịch đảo bằng cvInvert(). Thực tế lập trình tốt là dùng SV_SVD method cho nghịch đảo này vì một ngày bạn sẽ trạm chán một phân phối mà là một trong các eigenvalue là 0! cvMax và cvMaxS void cvMax( const CvArr* src1, const CvArr* src2, ); void cvMaxS( const CvArr* src, double value, CvArr* dst ); Hình 3-5. Một phân phối các điểm trong hai chiều với các ellipsoids xếp chồng biểu diễn khoảng cách Mahalonobis của 1.0, 2.0, và 3.0 từ hiệu dụng của phân phối cvMax() tính giá trị cực đại của cặp tương ứng các pixel trong các array src1 và src2. Với cvMaxS(), src array được so với scalar value hằng. Như luôn luôn, nếu mask là khác NULL thì chỉ các element của dst tương ứng với các entry khác không trong mask được tính. cvMerge void cvMerge( const CvArr* src0, const CvArr* src1, const CvArr* src2, const CvArr* src3, CvArr* dst ); cvMerge() là tác vị nghịch đảo của cvSplit(). Arrays trong src0, src1, src2, và src3 được kết hợp vào array dst. Dĩ nhiên, dst nên có cùng data type và size như tất cả trong các source array, nhưng có thể có hai, ba, hay bốn channel. Các source image không được dùng có thể được leftset thành NULL. cvMin và cvMinS void cvMin( const CvArr* src1, const CvArr* src2, CvArr* dst ); void cvMinS( const CvArr* src, double value, CvArr* dst ); cvMin() tính giá trị cực tiểu mỗi cặp tương ứng của các pixels trong arrays src1 và src2. Với cvMinS(), các src arrays được so với scalar value hằng. Lần nữa, nếu mask khác NULL thì chỉ các elements của dst tương ứng với các entry khác không trong mask được tính. cvMinMaxLoc void cvMinMaxLoc( const CvArr* arr, double* min_val, double* max_val, CvPoint* min_loc = NULL, CvPoint* max_loc = NULL, const CvArr* mask = NULL ); Routine này tìm các giá trị cự đại và cực tiểu trong array arr và (tùy chọn) trả về vị trí của chúng. Các giá trị cực đại và cực tiểu được tính được đặt trong min_val và max_val. Tùy chọn, các vị trí của các cực trị này sẽ cũng được viết vào các địa chỉ cho bởi min_loc và max_loc nếu các hía trị này là không NULL. Như bình thường, nếu mask là khác NULL vì chỉ các phần của image arr mà tương ứng với các nonzero pixels trong mask được quan tâm. cvMinMaxLoc() routine handles chỉ single-channel arrays, tuy nhiên, do đó nếu bạn có một multichannel array thì bạn nên dùng cvSetCOI() để set một channel cụ thể để quan tâm. cvMul void cvMul( const CvArr* src1, const CvArr* src2, CvArr* dst, double scale=1 ); cvMul() là multiplication function đơn giản. Nó nhân tất cả các elements trong src1 bởi các elements tương ứng trong src2 và sau đó đặt các kết quả trong dst. Nếu mask là non-NULL, thì bất kỳ element của dst mà tương ứng với một zero element của mask không được thay đổi bởi operation này. Không có function cvMulS() vì chức năng đó hiện được cấp bởi cvScale() hay cvCvtScale(). Một điều xa hơn cần nhớ: cvMul() thực hiện nhân phần từ với phần tử. Một ngày, khi bạn đang nhân vài matrix, bạn có thể bị lôi cuốn để đến với cvMul(). Điều này sẽ làm việc; nhớ rằng nhân matrix được làm với cvGEMM(), không với cvMul(). cvNot void( const CvArr* src, CvArr* dst ); Function cvNot() nghịch đảo mọi bit trong mọi element của src và sau đó đặt kết quả trong dst. Do đó, cho một 8-bit image value 0x00 sẽ được ánh xạ thành 0xff và value 0x83 sẽ được chiếu thành 0x7c. cvNorm double cvNorm( const CvArr* arr1, const CvArr* arr2 = NULL, int norm_type = CV_L2, const CvArr* mask = NULL ); Function này có thể được dùng để tính total norm của một array và cũng một lượng các norm khoảng cách tương đối nếu hai arrays được cung cấp. Trong trường hợp trước, norm được tính như được thấy trong Table 3-9. Table 3-9. Norm được tính bởi cvNorm() cho các giá trị khác nhau của khi arr2=NULL Norm_type CV_C Kết quả CV_L1 || arr1 || L1 abs(arr1x , y ) || arr1 ||c max x , y abs (arr1x , y ) x, y CV_L2 || arr1 || L 2 arr12x , y ) x, y Nếu array argument thứ hai arr2 là non-NULL, thì norm được tính là một norm khác—mà là, gì đó giống khoảng cách giữa hai arrays.* Trong ba trường hợp đầu tiên được thấy trong Table 3-10, norm là tuyệt đối; trong hai trường hợp sau nó được rescale bởi biên độ của array thứ hai arr2. Table 3-10. Norm được tính bởi cvNorm() cho các giá trị khác nhau của norm_type khi arr2 là non-NULL Norm_type Kết quả CV_C || arr1 arr 2 ||c max x , y abs(arr1x , y arr 2 x , y ) CV_L1 || arr1 arr 2 || L1 abs (arr1x , y arr 2 x , y ) x, y || arr1 arr 2 || x , y (arr1x , y arr 2 x , y ) 2 CV_L2 x, y CV_RELATIVE_C CV_RELATIVE_L1 || arr1 arr 2 ||C || arr 2 ||C || arr1 arr 2 || L1 || arr 2 || L 2 Trong tất cả trường hợp, arr1 và arr2 phải có cùng size và số channel. Khi có nhiều hơn một channel, norm được tính trên tất cả các channel cùng nhau (chẳng hạn các sum trong Tables 3-9 và 3-10 là không chỉ trên x và y mà còn trên các channel). cvNormalize cvNormalize( const CvArr* src, CvArr* dst, double a = 1.0, double b = 0.0, int norm_type = CV_L2, const CvArr* mask = NULL ); Như với nhiều OpenCV function, cvNormalize() làm nhiều hơn nó có thể ở đầu tiên xuất hiện. Phụ thuộc vào value của norm_type, image src được normalized hay ngược lại được chiếu vào dải cụ thể trong dst. Các giá trị có thể của norm_type được thấy trong Table 3-11. Table 3-11. Các giá trị có thể của norm_type argument cho cvNormalize() Norm_type CV_C Kết quả CV_L1 || arr1 || L1 abs( I x , y ) a || arr1 ||C max dst abs ( I x , y ) a dst CV_L2 || arr1 || L 2 I x2, y a CV_MINMAX Chiếu vào dải [a,b] dst Trong trường hợp của C norm, array src được rescaled thành biên độ của giá trị tuyệt đối của entry lớn nhất bằng với a. Trong trường hợp L1 hay L2 norm, array được rescaled sao cho norm được cho bằng với giá trị của a. Nếu norm_type được đặt thành CV_MINMAX, thì các giá trị của array được rescaled và translated sao cho chúng được chiếu tuyến tính vào khoảng giữa a và b (bao gồm). Như trước, nếu mask là non-NULL thì chỉ các pixels mà tương ứng với các nonzero values của mask image sẽ cấu thành tính toán của norm—và chỉ các pixels này sẽ được thay đổi bởi cvNormalize(). cvOr và cvOrS void cvOr( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=NULL ); void cvOrS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask = NULL ); Hai functions này tính bitwise OR operation trên array src1. Trong trường hợp của cvOr(), mỗi element của dst được tính như bitwise OR của hai elements tương ứng của src1 và src2. Trong trường hợp của cvOrS(), bitwise OR được tính với scalar value hằng. Như thường, nếu mask là non-NULL thì chỉ các elements của dst tương ứng với các nonzero entry trong mask được tính. Tất của data types được hỗ trợ, nhưng src1 và src2 phải có cùng data type cho cvOr(). Nếu các elements là floating-point type, thì biểu diễn bitwise của floating-point number đó được dùng. cvReduce CvSize cvReduce( const CvArr* src, CvArr* dst, int dim, int op = CV_REDUCE_SUM ); Reduction là biến đổi systematic của input matrix src vào một vector dst bởi áp dụng vài luật kết hợp op trên mỗi hàng (hay cột) và láng giêngf của nó đến khi chỉ một hàng (hay cột) vẫn thế (xem Table 3-12).* Argument op điều khiển cách reduction được làm, như tổng kết trong Table 3-13. Table 3-12. Argument op trong cvReduce() chọn reduction operator Value of op Result CV_REDUCE_SUM Compute sum across vectors CV_REDUCE_AVG Compute average across vectors CV_REDUCE_MAX Compute maximum across vectors CV_REDUCE_MIN Compute minimum across vectors Table 3-13. Argument dim trong cvReduce() điều khiển hướng của reduction Value of dim Result +1 Collapse to a single row 0 Collapse to a single column –1 Collapse as appropriate for dst cvReduce() hỗ trợ multichannel arrays của floating-point type. Nó cũng cho phép dùng một precision type cao hơn trong dst hơn xuất hiện trong src. Điều này thích hợp chính cho CV_REDUCE_SUM và CV_REDUCE_AVG, nơi overflows và các vấn đề cộng là có thể. cvRepeat void cvRepeat( const CvArr* src, CvArr* dst ); Function này chép nội dung của src vào dst, lặp lại bao nhiêu lần như cần thiết để điền dst. Về cụ thể, dst có thể là bất kỳ size tương ứng với src. Nó có thể là lớn hơn hay nhỏ hơn, và nó không cần có một số nguyên quan hệ giữa bất kỳ các chiều của nó và các chiều tương ứng của src. cvScale void cvScale( const CvArr* src, CvArr* dst, double scale ); Function cvScale() thực sự là một macro cho cvConvertScale() mà đặt shift argument thành 0.0. Do đó, nó có thể được dùng để rescale nội dung của một array và chuyển từ một kiểu của data type thành kiểu khác. cvSet và cvSetZero void cvSet( CvArr* arr, CvScalar value, const CvArr* mask = NULL ); Các function này set tất cả values trong tất cả channel của array thành giá trị cụ thể. cvSet() function nhận một mask argument tùy chọn: nếu mask được cung cấp, thì chỉ các pixels này trong image arr mà tương ứng với các nonzero values của mask image sẽ được đặt thành value chỉ định. Function cvSetZero() chỉ là một đồng nghĩa cho cvSet(0.0). cvSetIdentity void cvSetIdentity( CvArr* arr ); cvSetIdentity() đặt tất cả elements của array thành 0 ngoại trừ cho các element mà hàng và cột là bằng; các elements này được đặt thành 1. cvSetIdentity() hỗ trợ tất cả data types và không ngay cả đòi hỏi array là vuông. cvSolve int cvSolve( const CvArr* src1, const CvArr* src2, CvArr* dst, int method = CV_LU ); Function cvSolve() cung cấp một cách nhanh để giải các hệ tuyến tính dựa trên cvInvert(). Nó tính lời giải với C arg min x || A. X B || Trong đó A là square matrix cho bởi src1, B là vector src2, và C là lời giải tính bởi cvSolve() cho vector tốt nhất X nó có thể tìm thấy. Vector tốt nhất X đó được trả về trong dst. Cùng các method được hỗ trợ như bởi cvInvert() (mô tả trước đây); chỉ các floating-point data type được hỗ trợ. Function trả về một integer value trong đó một nonzero return nhận biết mà nó có thể tìm thấy một đáp án. Nên được lưu ý rằng cvSolve() có thể được dùng để giải overdetermined các hệ tuyến tính. Overdetermined systems sẽ được giải dùng gì đó gọi là pseudo-inverse, mà dùng các SVD method để tìm thấy đáp án least-squares cho hệ phương trình. cvSplit void cvSplit( const CvArr* src, CvArr* dst0, CvArr* dst1, CvArr* dst2, CvArr* dst3 ); Có các lần khi không thuận lợi để làm việc với một multichannel image. Trong các trường hợp này, ta có thể dùng cvSplit() để copy mỗi channel riêng thành một trrg vài single-channel images được cung cấp. cvSplit() function sẽ copy các channels trong src vào images dst0, dst1, dst2, và dst3 như cần thiết. Các destination images phải thỏa mãn source image theo size và data type nhưng, dĩ nhiên, nên là các single-channel images. Nếu source image có ít hơn bốn channels (như nó sẽ thường), thì các destination argument không cần thiết cho cvSplit() có thể được đặt thành NULL. cvSub void cvSub( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask = NULL ); Function này thực hiện một phép trừ phần tử phần tử cơ sở của một array src2 với một cái khác src1 và đặt kết quả trong dst. Nếu array mask là non-NULL, thì chỉ các elements này của dst tương ứng với các nonzero elements của mask được tính. Lưu ý rằng src1, src2, và dst phải tất cả có cùng type, size, và số các channels; mask, nếu được dùng, nên là một 8-bit array của cùng size và số các channels như dst. cvSub, cvSubS, và cvSubRS void cvSub( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask = NULL ); void cvSubS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask = NULL ); void cvSubRS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask = NULL ); cvSub() là một hàm trừ đơn giản; nó trừ tất cả các elements trong src2 với các elements tương ứng trong src1 và đặt các kết quả trong dst. Nếu mask là non-NULL, thì bất kỳ element của dst mà tương ứng với một zero element của mask không được thay đổi bởi tác vụ này. Function liên quan gần cvSubS() làm cùng việc ngoại trừ rằng một constant scalar value được thêm cho mỗi element của src. Function cvSubRS() là giống như cvSubS() ngoại trừ rằng, hơn việc trừ một hằng với mỗi element của src, nó trừ mọi element của src với value hằng. cvSum CvScalar cvSum( CvArr* arr ); cvSum() cộng tất cả pixels trong tất cả các channels của array arr. Nhận thấy rằng return value là kiểu CvScalar, mà có nghĩa rằng cvSum() có thể thích hợp các multichannel arrays. Trong trường hợp đó, tổng của mỗi channel được đặt trong thành phần tương ứng của CvScalar return value. cvSVD void cvSVD( CvArr* A, CvArr* W, CvArr* U = NULL, CvArr* V = NULL, int flags = 0 ); Singular value decomposition (SVD) là phân rã của một m-by-m matrix A thành dạng: A U W V T Trong đó W is a diagonal matrix và U và V là các matrix hợp nhất m-by-m và n-by-n. Dĩ nhiên matrix W cũng là m-by-n matrix, do đó ở đây “diagonal” có nghĩa rằng bất kỳ element mà số hàng và cottj là không bằng nhau thì nhất thiết bằng 0. Vì W nhất thiết là diagonal, OpenCV cho phép nó được biểu diễn một trong bởi một m-by-n matrix hay bởi một n-by-1 vector (mà trong trường hợp đó vector sẽ chứa chỉ các giá trị diagonal “singular”). Các matrix U và V là tùy chọn với cvSVD(), và nếu chúng được đặt thành NULL thì không có giá trị sẽ được trả về. Các argument flags cuối có thể là bất kỳ hay tất cả trong ba option được mô tả trong Table 3-14 (được kết hợp như thích hợp bằng Boolean hay operator). Table 3-14. Các flag có thể cho flags argument với cvSVD() Flag Result CV_SVD_MODIFY_A cho phép modification of matrix A CV_SVD_U_T Return UT thay vì U CV_SVD_V_T Return VT thay vì V cvSVBkSb void cvSVBkSb( const CvArr* W, const CvArr* U, const CvArr* V, const CvArr* B, CvArr* X, int flags = 0 ); Đây là một function mà bạn không thích gọi trực tiếp. Trong kết hợp với cvSVD() (vừa được mô tả), nó nằm dưới các SVD-based methods của cvInvert() và cvSolve(). Mà được bảo, bạn có thể muốn cut out người trung gian và làm các nghịch đảo matrix riêng (phụ thuộc vào data source, điều này có thể tiết kiệm cho bạn khỏi một chùm memory allocations cho các các matrix tạm bên trong của cvInvert() hay cvSolve()). Function cvSVBkSb() tính thay thế phía sau cho một matrix A mà được biểu diễn theo dạng của một phân rã của các matrix U, W, và V (chẳng hạn một SVD). Kết quả matrix X được cho bởi công thức: X V W * U T B Matrix B laf optional, và nếu được đặt thành NULL nó sẽ được bỏ qua. Matrix W* là một matrix mà các diagonal elements được định nghĩa bởi *i i1 cho λi ≥ ε. Giá trị ε này là singularity threshold, một số rất nhỏ mà điển hình tỉ lệ với tổng của các diagonal elements của W (chẳng hạn ii ) cvTrace CvScalar cvTrace( const CvArr* mat ); Trace của một matrix (Trace) là tổng tất cả các diagonal element. Trace trong OpenCV được thực hiện trên đinht của cvGetDiag() function, do đó nó không đòi hỏi array chuyển vào là vuông. Các Multichannel arrays được hỗ trợ, nhưng array mat nên là floating-point type. cvTranspose và cvT void cvTranspose( const CvArr* src, CvArr* dst ); cvTranspose() chép mọi element của src và vị trí trong dst nhận biết bởi row và column index. Function này hỗ trợ multichannel array; tuy nhiên, nếu bạn đang dùng multiple channels để biểu diễn các complex number, nhớ rằng cvTranspose() không thực hiện complex conjugation (một cách nhanh để hoàn thành nhiệm vụ này bởi các phương tiện của cvXorS() function, mà có thể được dùng để trực tiếp lật dấu các bit trong phần ảo của array). Macro cvT() đơn giản tiện tay cho cvTranspose(). cvXor và cvXorS void cvXor( const CvArr* src1, const CvArr* src2, CvArr* dst, const CvArr* mask=NULL ); void cvXorS( const CvArr* src, CvScalar value, CvArr* dst, const CvArr* mask=NULL ); Hai functions này tính bitwise XOR operation trên array src1. Trong trường hợp của cvXor(), mỗi element của dst được tính như bitwise XOR của hai elements tương ứng của src1 và src2. Trong trường hợp của cvXorS(), bitwise XOR được tính với constant scalar value. Lặp lại, nếu mask là non-NULL thì chỉ các elements của dst tương ứng với các nonzero entry trong mask được tính. Tất cả data types được hỗ trợ, nhưng src1 và src2 phải có cùng data type cho cvXor(). Cho các floating-point element, biểu diễn bitwise của floating-point number đó được dùng. cvZero void cvZero( CvArr* arr ); Function này đặt tất cả values trong tất cả channels của array thành 0. Drawing Things Điều mà thường xuyên xảy ra là cần vẽ vài thứ của picture hay vẽ gì đó lên đỉnh của một image có được từ đâu đó. Toward this end, OpenCV cung cấp một đám các functions mà sẽ cho phép ta làm các lines, squares, circles, và khác. Lines Đơn giản nhất trong những routines này là vẽ draws line bởi Bresenham algorithm [Bresenham65]: void cvLine( CvArr* array, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness = 1, int connectivity = 8 ); Argument đầu tiên cho cvLine() là CvArr* bình thường, mà trong ngữ cảnh này điển hình có nghĩa một IplImage* image pointer. Hai arguments tiếp thep là CvPoints. Như một gợi nhớ nhanh, CvPoint là một structure đơn giản chứa chỉ các integer members x và y. Ta có thể tạo một CvPoint “trực tiếp” bằng routine cvPoint(int x, int y), mà gòi thuận tiện hai integers vào một CvPoint structure cho ta. Argument tiếp, color, là kiểu CvScalar. CvScalars cũng là các structure, mà (bạn có thể nhớ lại) được định nghĩa như sau: typdef struct { double val[4]; } CvScalar; Như bạn có thể thấy, structure này chỉ là tập bốn số doubles. Trong trường hợp này, ba cái đầu tiên biểu diễn red, green, và blue channels; cái thứ tư không dùng (nó có thể được dùng cho một alpha channel khi thích hợp). Một điển hình làm việc dùng của handy macro CV_RGB(r, g, b). Macro này lấy ba số và gói chúng vào một CvScalar. Hai arguments tiếp theo là optional. Độ dày là độ dày của line (theo pixels), và sự kết đặt anti-aliasing mode. Mặc định là “8 connected”, mà sẽ cho một nice, smooth, anti-aliased line. Bạn có thể cũng đặt cái này thành “4 connected” line; các diagonal sẽ là thô và chắc, nhưng chúng sẽ được vẽ nhanh hơn nhiều. Dễ như cvLine() is cvRectangle(). Có lẽ không cần thiết nói với bạn rằng cvRectangle() vẽ một rectangle. Nó có cùng các argument như cvLine() ngoại trừ rằng không có connectivity argument. Đây là vì các rectangle kết quả luôn luôn được hướng bằng các cạnh song song với các trục xvà y-axes. Bằng cvRectangle(), ta đơn giản cho hai điểm cho các cạnh đối diện và OpenCV sẽ vẽ rectangle. void cvRectangle( CvArr* array, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness = 1 ); Circles và Ellipses Dễ tương tự là method để vẽ các circle, mà có cùng các argument. void cvCircle ( CvArr* array, CvPoint center, int radius, CvScalar color, int thickness = 1, int connectivity = 8 ); Cho các circle, các rectangle, và tất cả các đường kín khác, thickness argument có thể cũng được đặt thành CV_FILL, mà là một alias cho –1; kết quả là hình được vẽ sẽ được tô cùng màu như các cạnh. Chỉ phức tạp hơn một ít với cvCircle() là routine để vẽ các generalized ellipses: void cvEllipse( CvArr* img, CvPoint center, CvSize axes, double angle, double start_angle, double end_angle, CvScalar color, int thickness = 1, int line_type = 8 ); Trong trường hợp này, thành phần chính mới là axes argument, mà là type CvSize. Function CvSize rất giống CvPoint và CvScalar; nó là một structure đơn giản, trong trường hợp này chứa chỉ các member width và height. Như CvPoint và CvScalar, có một helper function thuận tiện cvSize(int height, int width) mà sẽ return một CvSize structure khi ta cần một cái. Trong trường hợp này, các height và width arguments biểu diễn length của các trục major và minor của ellipse. Angle là góc (theo độ) của major axis, mà được đo theo counterclockwise từ trục hoành (chẳng hạn từ x-axis). Tương tự start_angle và end_angle nhận diện (cũng theo độ) góc cho cung bắt đầu và cho nó hoàn thành. Do đó, cho một ellipse hoàn chỉnh bạn phải đặt những values này thành 0 và 360, tương ứng. Một cách lhacs để chỉ định vẽ một ellipse là to dùng một bounding box: void cvEllipseBox( CvArr* img, CvBox2D box, CvScalar color, int thickness = 1, int line_type = 8, int shift= 0 ); Ở đây lần nữa ta thấy một cái khác của các helper structures của OpenCV, CvBox2D: typdef struct { CvPoint2D32f center; CvSize2D32f size; float angle; } CvBox2D; CvPoint2D32f là tương tự floating-point của CvPoint, và CvSize2D32f là tương tự floatingpoint của CvSize. Nhưng cái này, cùng với góc nghiêng, chỉ định hiệu quả bounding box cho ellipse. Polygons Cuối cùng, ta có một tập các functions để vẽ các polygon: void cvFillPoly( CvArr* img, CvPoint** pts, int* npts, int contours, CvScalar color, int line_type = 8 ); void cvFillConvexPoly( CvArr* img, CvPoint* pts, int npts, CvScalar color, int line_type = 8 ); void cvPolyLine( CvArr* img, CvPoint** pts, int* npts, int contours, int is_closed, CvScalar color, int thickness = 1, int line_type = 8 ); Tất cẩ ba cái này là các biến đổi ít trên cùng ý tưởng, với khác biệt chính là cách các điểm được chỉ định. Trong cvFillPoly(), các điểm được cung cấp như một array của các CvPoint structure. Điều này cho phép cvFillPoly() vẽ nhiều polygons trong một lời gọi đơn. Tương tự npts là một array của point counts, một cho mỗi polygon được vẽ. Nếu biến is_closed được đặt thành true, thì một segment thêm sẽ được vẽ từ điểm cuối đến điểm đầu cho mỗi polygon. cvFillPoly() là rất mạnh và sẽ handle các polygon tự cắt, polygons với các lỗ, và các phức tạp khác như thế. Không may, điều này có nghĩa routine tương đối chậm. cvFillConvexPoly() làm việc giống cvFillPoly() ngoại trừ rằng nó vẽ một polygon một lúc và có thể draw chỉ các convex polygon.* Thuận lợi mà là cvFillConvexPoly() chạy nhanh hơn. Function thứ ba, cvPolyLine(), lấy cùng arguments như cvFillPoly(); tuy nhiên, do chỉ các cạnh polygon được vẽ, tự giao biểu diễn không sự phức tạp cụ thể. Do đó function này nhanh hơn nf so với cvFillPoly(). Fonts và Text Một dạng cuối của vẽ là ái có thể cần là vẽ text. Dĩ nhiên, text tạo tập riêng các phức tạp, nhưng—như luôn luôn với kiểu này của việc—OpenCV được liên quan nhiều hơn bằng cung cấp một giải pháp đơn giản “thấp và dirty” mà sẽ làm cho các trường hợp đơn giản cases hơn là một giải pháp mạnh, phức tạp (mà sẽ là dữ được cho bởi các khả năng của các thư viện khác). OpenCV có một routine chính, gọi là cvPutText() mà chỉ ném vaiif text lên một image. Text nhận diện bởi text được in bằng lower-leftcorner của nó của text box ở gốn và theo màu chỉ định bởi color. void cvPutText( CvArr* img, const char* text, CvPoint origin, const CvFont* font, CvScalar color ); Luôn luôn có vài thứ mà làm việc của ta phức tạp thêm một tí hơn ta muốn, và trong trường hợp này việc có mặt của pointer đến CvFont. Trong một nutshell, cách để lấy CvFont* pointer hợp lệ để gọi function cvInitFont(). Function này lấy một nhóm các arguments mà câuus hình vài font cụ thể để dùng trên screen. Những cái bạn quen với GUI programming trong các môi trường khác sẽ tìm thấy cvInitFont() là nhớ lại các device tương tự nhưng với vài option. Để tạo một CvFont mà ta có thể chuyển vào cvPutText(), ta phải đầu tiên khai báo một CvFont variable; thì ta có thể chuyển nó vào cvInitFont(). void cvInitFont( CvFont* font, int font_face, double hscale, double vscale, double shear = 0, int thickness = 1, int line_type = 8 ); Quan sát rằng đây là một khác biệt nhỏ về cách các function dường như tương tự, chẳng hạn cvCreateImage(), làm việc trong OpenCV. Lời gọi đến cvInitFont() khởi tạo CvFont structure hiện có (mà có nghĩa rằng bạn tạo variable và chuyển cho cvInitFont() một pointer vào variable bạn tạo). Đây không giống cvCreateImage(), mà tạo structure cho bạn và trả về một pointer. Argument font_face là một trong những cái được kê trong Table 3-15 (và mô tả trong Figure 3-6), và nó có thể tùy chọn được kết hợp (bởi Boolean OR) bằng CV_FONT_ITALIC. Table 3-15. Available fonts (all are variations of Hershey) Identifier Mô tả CV_FONT_HERSHEY_SIMPLEX Normal size sanserif CV_FONT_HERSHEY_PLAIN Small size sanserif CV_FONT_HERSHEY_DUPLEX Normal size sanserif, more complex than CV_FONT_HERSHEY_SIMPLEX CV_FONT_HERSHEY_COMPLEX Normal CV_FONT_HERSHEY_DUPLEX CV_FONT_HERSHEY_TRIPLEX Normal CV_FONT_HERSHEY_COMPLEX CV_FONT_HERSHEY_COMPLEX_SMALL CV_FONT_HERSHEY_COMPLEX CV_FONT_HERSHEY_SCRIPT_SIMPLEX CV_FONT_HERSHEY_SCRIPT_COMPLEX CV_FONT_HERSHEY_SCRIPT_SIMPLEX size serif, more complex than size serif, more complex than Smaller version of Handwriting style More complex variant of Figure 3-6. Tám fonts của Table 3-15 vẽ với hscale = vscale = 1.0, vói gốc của mỗi line được chia theo chiều đứng 30 pixels Cả hscale và vscale có thể được đặt thành một trong 1.0 hay 0.5 only. Điều này làm font được vẽ ở height (và width) đủ hay nửa tương ứng với định nghĩa cơ bản của font cụ thể. Shear function tạo nghiêng cho font; nếu đặt thành 0.0, font không nghiêng. Nó có thể được đặt lớn bằng 1.0, mà đặt slope của các characters xấp xỉ 45 độ. Cả thickness và line_type là giống như được định nghĩa cho tất cả các drawing function khác. Data Persistence OpenCV cung cấp một cơ chế để serializing và de-serializing các data types khác nhau vào và ra disk theo một trong YAML hay XML format. Trong chương về HighGUI, mà chuyên về các user interface function, ta sẽ đề cập các function cụ thể mà lưu và nhớ lại hầu hết object phổ biến: IplImages (các functions này là cvSaveImage() và cvLoadImage()). Thêm vào đó, chương HighGUI sẽ thảo luận các read và write functions cụ thể cho movies: cvGrabFrame(), mà đọc từ file hay từ camera; và cvCreateVideoWriter() và cvWriteFrame(). Trong phần này, ta sẽ tập tring vào lưu object chung: reading và writing các matrix, OpenCV structures, và configuration và log files. Đầu tiên ta bắt đầu với các functions cụ thể và thuận lợi mà save và load các matrix OpenCV. những functions này là cvSave() và cvLoad(). Giả thiết bạn có một 5-by-5 identity matrix (0 mọi nơi ngoại trừ cái đầu trên diagonal). Example 3-15 cho thấy cách hoàn thành điều này. Example 3-15. Saving và loading a CvMat CvMat A = cvMat( 5, 5, CV_32F, the_matrix_data ); cvSave( “my_matrix.xml”, &A ); ... // to load it then in some other program dùng … CvMat* A1 = (CvMat*) cvLoad( “my_matrix.xml” ); CxCore reference manual chứa toàn bộ phần về data persistence. Điều bạn thực sự cần biết là data persistence chung trong OpenCV gồm tạo một CvFileStorage structure, như trong Example 316, mà lưu các memory objects trong một tree structure. Bạn có thể tạo và điều structure này bởi reading từ disk qua cvOpenFileStorage() với CV_STORAGE_READ, hay bạn có thể tạo và open CvFileStorage qua cvOpenFileStorage() với CV_STORAGE_WRITE để writing và sau đó điền nó dùng các data persistence function thích hợp. Trên disk, data được lưu theo một XML hay YAML format. Example 3-16. CvFileStorage structure; data is accessed by CxCore data persistence functions typedef struct CvFileStorage { ... // hidden fields } CvFileStorage; Data nội bên trong CvFileStorage tree có thể gồm một tập thứ bâc các scalars, các CxCore objects (các matrix, sequences, và graphs) và/hoặc user-defined objects. Hãy nói bạn có một configuration hay logging file. Ví dụ, xem trường hợp của một movie configuration file mà nói cho ta bao nhiêu frames ta muốn (10), size của chúng là (320 by 240) và mmm 3-by-3 color conversion matrix mà sẽ được áp dụng. Ta muốn gọi file “cfg.xml” trên disk. Example 3-17 cho thấy cách làm điều này. Example 3-17. Writing a configuration file “cfg.xml” to disk CvFileStorage* fs = cvOpenFileStorage( “cfg.xml”, 0, CV_STORAGE_WRITE ); cvWriteInt( fs, “frame_count”, 10 ); cvStartWriteStruct( fs, “frame_size”, CV_NODE_SEQ ); cvWriteInt( fs, 0, 320 ); cvWriteInt( fs, 0, 200 ); cvEndWriteStruct(fs); cvWrite( fs, “color_cvt_matrix”, cmatrix ); cvReleaseFileStorage( &fs ); Lưuys vào key function trong ví dụ này. Ta có thể cho tên thành các integer mà ta viết vào structure dùng cvWriteInt(). Ta có thể tạo một structure tùy ý, dùng cvStartWriteStruct(), mà cũng được cho một optional name (chuyển 0 hay NULL nếu không có tên). Structure này có hai int mà không có tên và do đó ta chuyển 0 cho chúng trong name field, sau khi ta dùng cvEndWriteStruct() để kết thúc viết structure đó. Nếu có nhiều structure hơn, ta Start và End từng cái tương tự; các structures có thể được kết lại theo độ sâu tùy ý. Ta sau đó dùng cvWrite() để viết ra color conversion matrix. Trái với thủ tục viết matrix hoàn toàn phức tạp này bằng đơn giản hơn cvSave() trong Example 3-15. cvSave() function chỉ là một shortcut thuận lợi cho cvWrite() khi bạn có chỉ một matrix để viết. Khi ta hoàn thành viết data, CvFileStorage handle được giải phóng trong cvReleaseFileStorage(). Output (ở đây, theo dạng XML) sẽ trong như trong Example 3-18. Example 3-18. XML version of cfg.xml on disk <?xml version=“1.0”?> <opencv_storage> <frame_count>10</frame_count> <frame_size>320 200</frame_size> <color_cvt_matrix type_id=“opencv-matrix”> <rows>3</rows> <cols>3</cols> <dt>f</dt> <data>…</data></color_cvt_matrix> </opencv_storage> Ta có thể sau đó đọc configuration file này như được thấy trong Example 3-19. Example 3-19. Reading cfg.xml from disk CvFileStorage* fs = cvOpenFileStorage( “cfg.xml”, 0, CV_STORAGE_READ ); int frame_count = cvReadIntByName( fs, 0, “frame_count”, 5 /* default value */ ); CvSeq* s = cvGetFileNodeByName(fs,0,“frame_size”)->data.seq; int frame_width= cvReadInt( (CvFileNode*)cvGetSeqElem(s,0) ); int frame_height = cvReadInt( (CvFileNode*)cvGetSeqElem(s,1) ); CvMat* color_cvt_matrix = (CvMat*) cvReadByName( fs, 0, “color_cvt_matrix” ); cvReleaseFileStorage( &fs ); Khi đọc, ta mở XML configuration file bằng cvOpenFileStorage() như trong Example 3-19. Ta sau đó đọc frame_count dùng cvReadIntByName(), mà cho phép một value mặc định được cho nếu không có số được đọc. Trong trường hợp này mặc định là 5. Ta sau đó lấy structure mà ta đặt tên “frame_size” dùng cvGetFileNodeByName(). Từ đây, ta đọc hai integer không có tên dùng cvReadInt(). Tiếp theo ta đọc color conversion matrix có tên dùng cvReadByName().* Lần nữa, trái với điều này dạng ngắn short cvLoad() trong Example 3-15. Ta có thể dùng cvLoad() nếu ta chỉ có một matrix để đọc, nhưng ta phải dùng cvRead() nếu matrix được nhúng vào trong một structure lớn hơn. Cuối cùng, ta release CvFileStorage structure. List các data persistence function thích hợp kết hợp với CvFileStorage structure được thấy trong Table 3-16. Xem CxCore manual cho chi tiết hơn. Table 3-16. Data persistence functions Function Mô tả Open và Release cvOpenFileStorage Opens file storage for reading hay writing cvReleaseFileStorage Releases data storage Writing cvStartWriteStruct Starts writing a new structure cvEndWriteStruct Ends writing a structure cvWriteInt Writes integer cvWriteReal Writes float cvWriteString Writes text string cvWriteComment Writes an XML hay YAML comment string cvWrite Writes một object chẳng hạn a CvMat cvWriteRawData Writes multiple numbers cvWriteFileNode Writes file node to một cái khác file storage Table 3-16. Data persistence functions (continued) Function Mô tả Reading cvGetRootFileNode Gets the top-level nodes of the file storage cvGetFileNodeByName Finds node in the map hay file storage cvGetHashedKey trả về a unique pointer for given name cvGetFileNode Finds node in the map hay file storage cvGetFileNodeName trả về name of file node cvReadInt Reads unnamed int cvReadIntByName Reads named int cvReadReal Reads unnamed float cvReadRealByName Reads named float cvReadString Retrieves text string from file node cvReadStringByName Finds named file node và trả về its value cvRead Decodes object và trả về pointer to it cvReadByName Finds object và decodes it cvReadRawData Reads multiple numbers cvStartReadRawData Initializes file node sequence reader cvReadRawDataSlice Reads data from sequence reader above Function Mô tả Reading cvGetRootFileNode Gets the top-level nodes of the file storage cvGetFileNodeByName Finds node in the map hay file storage cvGetHashedKey trả về a unique pointer for given name cvGetFileNode Finds node in the map hay file storage cvGetFileNodeName trả về name of file node cvReadInt Reads unnamed int cvReadIntByName Reads named int cvReadReal Reads unnamed float cvReadRealByName Reads named float cvReadString Retrieves text string from file node cvReadStringByName Finds named file node và trả về its value cvRead Decodes object và trả về pointer to it cvReadByName Finds object và decodes it cvReadRawData Reads multiple numbers cvStartReadRawData Initializes file node sequence reader cvReadRawDataSlice Reads data from sequence reader above Integrated Performance Primitives Intel có một sản phẩm gọi là Integrated Performance Primitives (IPP) library (IPP). Thư viện này thực sự là một toolbox của các lõi hiệu suất cao để handling multimedia và các tác vụ tiêu thụ processor khác theo cách mà làm mở rộng việc dùng kiến trúc chi tiết của các processor của họ (và, một thỏa thuận tệ hại, các processors của các nhà sản xuất khác mà có cùng kiến trúc). Như được thảo luận trong chương 1, OpenCV thích quan hệ gần với IPP, cả hai ở mức phần mềm và ở một mức tổ chức bên trong của công ty này. Như một kết quả, OpenCV được thiết kế để tự động nhận ra sự có mặt của IPP library và tự động “hoàn đổi” các thực hiện hiệu suất thấp của nhiều chức năng lõi thành các higher-performance counterparts trong IPP. IPP library cho phép OpenCV lấy các thuận lợi của tối ưu hiệu suất mà đến từ các lệnh SIMD trong một single processor cũng như từ các kiến trúc multicore hiện đại. Với những cơ bản này trong tay, ta có thể thực hiện lượng lớn các nhiệm vụ cơ bản. Tiến lên qua text này, ta sẽ quan sát nhiều khả năng phức tạp hơn của OpenCV, hầu hết tất cả mà được tạo trên những routines này. Nó sẽ không ngạc nhiên rằng image processing—mà thường đòi hỏi làm cùng thứ trên lượng lớn data, mà phần lớn là hoàn toàn parallel—mà hiện thực một thuận lợi lớn từ bất kỳ code mà cho phép nó có thuận lợi của cac đơn vị thực thi song song của bất kỳ dạng nào (MMX, SSE, SSE2, etc.). Verifying Installation Cách kiểm tra và đảm bảo rằng IPP được cài và làm việc thích hợp bằng function cvGetModuleInfo(), được thấy trong Example 3-20. Function này sẽ nhận cả version của OpenCV bạn hiện đang chạy và version và nhận bất kỳ add-in modules. Example 3-20. dùng cvGetModuleInfo() to check for IPP char* libraries; char* modules; cvGetModuleInfo( 0, &libraries, &modules ); printf(“Libraries: %s/nModules: %s/n”, libraries, modules ); Code trong Example 3-20 sẽ tạo các text string mà mô tả các installed libraries và modules. Output có thể trông như thế này: Libraries cxcore: 1.0.0 Modules: ippcv20.dll, ippi20.dll, ipps20.dll, ippvm20.dll Các modules kê trong output này là các IPP modules dùng bởi OpenCV. Các modules này tự chúng thực sự xử lý cho ngay cả các lower-level CPU-specific libraries. Chi tiết cách nó tất cả làm việc nằm ngoài phạm vi sách này, nhưng nếu bạn thấy các IPP libraries trong Modules string thì bạn có thể be pretty confident that mọi thứ is working như mong đợi. Dĩ nhiên, bạn có thể dùng thông tin này to kiểm tra that IPP is running correctly on your own system. Bạn có thể cũng dùng it to check for IPP on a machine on which your finished software is installed, perhaps then making some dynamic adjustments phụ thuộc vào có hay không IPP is available. Tóm tắt In chương này ta introduced some basic data structures that ta sẽ often trạm chán. Về cụ thể, ta met the OpenCV matrix structure và the all-important OpenCV image structure, IplImage. ta considered both in some detail và found that the matrix và image structures are rất similar: the functions used for primitive manipulations in one work equally well in the other. Exercises In the following exercises, bạn có thể need to refer to the CxCore manual that ships with OpenCV hay to the OpenCV Wiki on the Web for details of the functions outlined in chương này. Find và 1. d open .../opencv/cxcore/include/cxtypes.h. Read through và tìm thấy the many conversion helper functions. a. , and then take its ceiling và floor. b. Generate some random numbers. c. tạo a floating point CvPoint2D32f và convert it to an integer CvPoint. d. Convert a CvPointer đến a CvPoint2D32f. 2. This exercise sẽ accustom bạn to the idea of nhiều functions taking matrix types. Create a two-dimensional matrix with three channels of type byte with data size 100-by-100. Set tất cả the values to 0. a. Draw a circle in the matrix dùng void cvCircle( CvArr* img, CvPoint center, intradius, CvScalar color, int thickness=1, int line_type=8, int shift=0 ). b. Display this image dùng methods described in chương 2. 3. tạo a two-dimensional matrix with three channels of type byte with data size 100-by-100, và set tất cả the values to 0. dùng the pointer element truy cập function cvPtr2D to trỏ đến the middle (“green”) channel. Draw a green rectangle between (20, 5) và (40, 20). 4. tạo a three-channel RGB image of size 100-by-100. Clear it. dùng pointer arithmetic to draw a green square giữa (20, 5) và (40, 20). 5. Practice dùng region of interest (ROI). tạo a 210-by-210 single-channel byte image and zero it. With in the image, build a pyramid of increasing values dùng ROI and cvSet(). That is: the outer border should be 0, the next inner border should be 20, the next inner border should be 40, và so on until the final innermost square is set to value 200; tất cả borders should be 10 pixels wide. Display the image. 6. dùng multiple image headers for one image. Load một image that is tối thiểu 100-by-100. Create hai additional image headers và set their origin, depth, number of channels, and widthstep to be the same as the loaded image. In the new image headers, set the widthat 20 và the height at 30. Cuối cùng, set their imageData pointers to point to the pixel at (5, 10) và (50, 60), respectively. Pass những cái này new image subheaders to cvNot(). Display the loaded image, mà should have hai inverted rectangles with in the larger image. 7. tạo a mask dùng cvCmp(). Load a real image. dùng cvSplit() to split the image into red, green, và blue images. a. tìm thấy và display the green image. b. Clone this green plane image twice (call những cái này clone1 và clone2). c. tìm thấy the green plane’s minimum và maximum value. d. Set clone1’s values to thresh = (unsigned char)((maximum - minimum)/2.0). e. Set clone2 to 0 và dùng cvCmp(green_image, clone1, clone2, CV_CMP_GE). Now clone2 sẽ have a mask of where the value exceeds thresh in the green image. f. e cvSubS(green_image,thresh/2, green_image, clone2) và display the results. 8. tạo a structure of an integer, a CvPoint và a CvRect; call it “my_struct”. a. Write hai functions: void write_my_struct( CvFileStorage * fs, const char * name, my_struct *ms) và void read_my_struct( CvFileStorage* fs, CvFileNode* ms_node, my_struct* ms ). dùng them to write và read my_struct. b. Write và read an array of 10 my_struct structures.