CHAPTER 2
Introduction to OpenCV Getting Started
Sau khi cài đặt OpenCV librar rary, nhi ệm v ụ đ ầu tiên c ủa ta, t ự nhiên, đ ể b ắt đ ầu và làm gì đó hấp dẫn. Để làm đi ều này, ta s ẽ c ần thiết l ập môi tr ường l ập trình. Tro Trong ng Visu Visual al Stud Studio io,, đi ều c ần đ ểt ạo m ột pro proje ject ct và c ấu hìn hình h setu setup p sao sao cho cho (a) (a) các các th ư viện highgui.lib, highgui.lib, cxcore.lib, cxcore.lib, ml.lib, ml.lib, và cv.lib được liê liên kết* và (b) prep reproc rocessor sẽ tìm tìm OpenCV …/opencv/*/include directories cho các header files. Các “include” directories sẽ điển hìn hình h có có tên tên gì đó như C:/program files/opencv/cv/include, files/opencv/cv/include, …/opencv/cxcore/include,, …/opencv/ml/include …/opencv/cxcore/include …/opencv/ml/include,, và …/opencv/otherlibs/highgui …/opencv/otherlibs/highgui.. Một khi bạn làm xong điều này, bạn có th ể tạo m ột C file m ới và b ắt đ ầu program đ ầu tiên.
tr Lưu ý: ý: Cá Các he header file ch chính ính có có th thể làm bạ n dễ thở hơ n nh nhiề u. Nh Nhiề u ma macro hữ u íc ích trong cá các header files …/opencv/cxcore/include/cxtypes.h và cxmisc.h. cxmisc.h . Nh Những cái cái này này có có th thể làm làm các các thứ như khởi tạo cá các st struct ructur ures es và arra rrays tro trong ng một dò dòng, ng, so sort lis lists, ts, và và hơn nữa. Hầu hết cá các header headers s quan quan trọng cho cho biên biên dịch are are .../cv/include/cv.h và …/cxcore/include/cxcore.h cho computer vision, …/otherlibs/highgui/highgui.h để I/O, và …/ml/include/ml.h để machin hine learning.
First Program—Display a Picture
OpenCV cung cấp các ti ện ích đ ể đ ọc t ừ m ột m ảng l ớn các image file type c ũng nh ư t ừ vid video và came ameras ras. Các tiện ích là m ột ph ần c ủa m ột tool oolkit g ọi là Hi HighGU ghGUII, mà là đ ược incl includ ude e tro trong ng Open OpenCV CV packa ackage ge.. Ta sẽ dùng dùng vài vài tro trong ng những tiện íc ích để t ạo một prog progra ram m đơn giản mà mở một imag image e và hiện nó trên trên scree creen. n. Xem Xem Ví d ụ 2-1. -1. Ví dụ 2-1. 2-1. Một Ope OpenC nCV V pro progr gram am đơ n gi giả n mà mà tả i mộ t ima image ge từ disk disk và disp displa lays ys nó trên trên scre screen en #include “highgui.h” int main( int argc, char** argv ) { IplImage* img = cvLoadImage( argv[1] ); cvNamedWindow( “Example1”, CV_WINDOW_AUTOSIZE ); cvShowImage( “Example1”, img ); cvWaitKey(0); cvReleaseImage( &img ); cvDestroyWindow( “Example1” ); }
Khi biên dịch và chạy t ừ comm ommand and line v ới m ột tham s ố đ ơn, prog rogram này t ải ảnh t ừ memo memory ry và dis displ plays ays nó trên trên scre screen en.. Nó Nó sau sau đó đó đợi đến khi khi use userr nhấn m ột phím phím,, mà mà là lúc những đó đóng cửa sổ và th thoát. Hãy đi qu qua pro prog gram từng dò dòng và mất một lúc để hiểu đi ều m ỗi l ệnh làm. IplImage* IplImage* img = cvLoadImage( argv[1] );
Dòng Dòng này tải image. image.** Funct Function ion cvLoadImage() là một high-level high-level routine mà dò file format format được tải dựa trên file name; nó c ũng t ự động c ấp memory cần cho image data stru struc cture. re. Lưu ý rằng cvLoadImage() có th ể đọc lượng lớn các image format, gồm BMP, DIB, JPEG, JPEG, JPE, PNG, PBM, PBM, PGM, PPM, SR, RAS, RAS, và TIFF. TIFF. M ột pointer pointer đ ế n image data structure được cấp sau đó được trả về. Struc Structu ture re này, này, gọi là là IplImage, là Op OpenCV cons onstruc ruct mà với nó bạn s ẽ làm việc nhiều nhất. Open OpenCV CV dùng dùng stru struct ctur ure e này này để hand handle le t ất cả các các ki ki ểu của ima image ge:: sin singl glee-ch chan anne nel, l, multichannel, integer-valued, floating-point-valued, et cetera. Ta dùng pointer mà cvLo cvLoad adIm Imag age() e() tr trả về để thao tác image image và image image data. data. cvNamedWindow( “Example1”, CV_WINDOW_AUTOSIZE );
Một high-le high-level vel func function tion khác, khác, cvNamedWindow(), mở một win windo dow w trên trên scre screen en mà có thể chứa và dis display một image age. Func unction này, ày, đ ược cấp b ởi HighGU GUII libra ibrary ry,, c ũng đặt tê tên
cho window (trong trường hợp này, “Example1”). Các lời gọi HighGUI tương lai mà tương tác với window này sẽ refer đến nó theo tên này. Argument thứ hai cho cvNamedWindow() định nghĩ a các window properties. Nó có th ể được đặt một trong thành 0 (value mặc định) hay thành CV_WINDOW_AUTOSIZE. Trong trường hợp trước, size của window sẽ là giống nhau bất ch ấp image size, và image s ẽ được chỉnh vừa bên trong window. Trong trường hợp sau, window sẽ mở rộng hay thu hẹp tự động khi image được tải sao cho vừa với size th ực c ủa image. cvShowImage( “Example1”, img );
Bất cứ khi nào ta có một image theo dạng của một IplImage* pointer, ta có thể display nó trong một window hiện có bằng cvShowImage(). cvShowImage() function đòi hỏi một window có tên hiện t ồn t ại (t ạo b ởi cvNamedWindow()). Trên lời gọi đế n cvShowImage(), window sẽ được redrawn bằng image thích hợp trong nó, và window sẽ t ự resize để thích hợp nếu nó được tạo dùng cờ CV_WINDOW_AUTOSIZE. cvWaitKey(0);
cvWaitKey() function yêu c ầ u program stop và đ ợi m ột keystroke. N ếu m ột positive
argument được cho, program sẽ đợi số milliseconds đó và sau đó ti ếp t ục ngay c ả không có gì được nhấn. Nếu argument được đặt thành 0 hay số âm, program sẽ đợi vô thời hạn một keypress. cvReleaseImage( &img );
Một khi ta đi qua image, ta có thể t ự do v ới memory đ ược c ấp. OpenCV mong m ột pointer đế n IplImage* pointer cho hoạt động này. Sau khi lời g ọi đ ược hoàn thành, pointer img sẽ được đặt thành NULL. cvDestroyWindow( “Example1” );
Cuối cùng, ta có thể destroy chính window. Function cvDestroyWindow() sẽ close window và de-allocate bất kỳ việc dùng memory được cấp (g ồm image buffer nội c ủa window , mà đang giữ một copy của thông tin pixel từ *img ). Cho một program đơn giản, bạn không thực sự phải gọi cvDestroyWindow() hay cvReleaseImage() vì tất cả resources và windows của application được đóng tự động bởi operating system ngay khi thoát, nhưng đó là thói quen tố t. Bây giờ ta có program đơn giản này ta có thể chơi với nó theo vài cách, nh ưng ta không muốn tự làm khó. Nhiệm vụ tiếp theo sẽ là dựng một chupwng trình rất đơn gi ản — h ầu h ết đ ơn gi ản nh ư cái này—program đ ể đ ọc và display m ột AVI video file. Sau đó, ta sẽ bắt đầu hàn gắn nhiều hơn một tí.
Second Program—AVI Video/*/
Playing một video với OpenCV là h ầu h ết d ễ nh ư displaying m ột picture đ ơn. Ch ỉ v ấn đ ề ta đ ối di ện là ta c ần vài ki ểu l ặp đ ể đ ọc t ừng frame theo trình t ự; ta có th ể c ũng c ần cách để ra khỏi vòng lặp đó nếu movie quá chán. Xem Ví dụ 2-2. Example 2-2. A đơn giản OpenCV program for playing a video file from disk #include “highgui.h” int main( int argc, char** argv ) { cvNamedWindow( “Example2”, CV_WINDOW_AUTOSIZE ); CvCapture* capture = cvCreateFileCapture( argv[1] ); IplImage* frame; while(1) { frame = cvQueryFrame( capture ); if( !frame ) break; cvShowImage( “Example2”, frame ); char c = cvWaitKey(33); if( c == 27 ) break; } cvReleaseCapture( &capture ); cvDestroyWindow( “Example2” ); } } }
Ở đây ta bắt đầu function main() bằng tạo bình thườ ng một window có tên, trong trường hợp này “Example2”. Các thứ có một ít hấp d ẫn sau đó. CvCapture* capture = cvCreateFileCapture( argv[1] );
Function cvCreateFileCapture() lấ y argument là tên của AVI file đ ược tải và sau đó tr ả v ề một pointer đế n CvCapture structure. Structure này chứa tất cả thông tin về AVI file đang được đọc, gồm thông tin trạng thái. Khi t ạo theo cách này, CvCapture structure được bởi tạo đến bắt đầu của AVI. frame = cvQueryFrame( capture );
file. cvQueryFrame() l ấ y Một khi bên trong của while(1) loop, ta bắt đ ầu đọc từ AVI argument của nó một pointer đế n CvCapture structure. Nó sau đó grabs video frame tiếp vào memory (memory mà thực sự là một phần của CvCapture structure). Một pointer được trả về cho frame đó. Không giống cvLoadImage , mà thực sự cấ p memory cho image, cvQueryFrame dùng memory đã được cấ p trong CvCapture structure. Do đó nó sẽ không cần (hay wise) g ọi cvReleaseImage() cho “frame” pointer này. Thay vào đó, frame image memory sẽ được giải phóng khi CvCapture structure được giải phóng. c = cvWaitKey(33); if( c == 27 ) break;
Một khi ta đã displayed frame, ta sau đó đợi 33 ms.* Nếu user bấm một key, thì c sẽ được đặt thành ASCII value c ủa key đó; nếu không, thì nó s ẽ đ ược đ ặt thành –1. N ếu user bấm Esc key (ASCII 27), thì ta sẽ thoát read loop. Ngược l ại, 33 ms s ẽ qua và ta s ẽ chỉ thực thi loop lại. Không có gì đáng kể, trong ví dụ đơn gi ản này, ta rõ ràng không đi ều khi ển t ốc đ ộ c ủa video theo bất kỳ cách thông minh nào. Ta dự a vào vai trò trong cvWaitKey() để nhịp loading các frame. Trong ứng dụng phức tạp hơn sẽ khôn ngoan khi đọc frame rate thực từ CvCapture structure (từ AVI) và hành động phf hợp! cvReleaseCapture( &capture );
Khi ta thoát read loop—vì không có nhi uềvideo data h nơhay vì user b mấ Esc key—ta có thể free memory kết hợp với CvCapture structure. Điều này cũng sẽ đóng bất k ỳ open file handles đế n AVI file.
Moving Around
OK, tuyệt. Bây giờ là lúc k ết n ối, t ăng c ướng các toy program c ủa ta, và khám phá m ột ít v ề kh ả n ăng s ẵn có. Đi ều đ ầu tiên ta có th ể thông báo v ề AVI player c ủa Example 2-2 là nó không có cách di chuyển nhanh chóng bên trong video. Nhiệm vụ tiếp theo s ẽ là thêm một slider bar, mà sẽ cho ta khả năng này. HighGUI toolkit cung cấp m ột số công cụ đ ơn giản đ ể làm vi ệc v ới các image và video là slider, hơn các hàm display đơn giản ta vừa giới thiệu. M ột c ơ ch ế đ ặc bi ệt h ữu ích mà cho phép ta nhảy dễ dàng từ một phần của video đến phần khác. Để tạo một slider, ta gọi cvCreateTrackbar() và nhận diện window mà ta muốn xuất hiện trong. Để lấy chức năng mong muốn, ta chỉ cần cung cấp m ột callback mà s ẽ th ực hi ện relocation. Example 2-3 cho các chi tiế t. Example 2-3. Program thêm trackbar slider vào viewer window cơ bả n: khi slider bị di chuyể n, function onTrackbarSlide() được gọi và sau đó chuyển giá trị mới của slide #include “cv.h” #include “highgui.h” int g_slider_position = 0; CvCapture* g_capture = NULL; void onTrackbarSlide(int pos) { cvSetCaptureProperty( g_capture, CV_CAP_PROP_POS_FRAMES, pos ); } int main( int argc, char** argv ) { cvNamedWindow( “Example3”, CV_WINDOW_AUTOSIZE ); g_capture = cvCreateFileCapture( argv[1] ); int frames = (int) cvGetCaptureProperty( g_capture, CV_CAP_PROP_FRAME_COUNT ); if( frames!= 0 ) { cvCreateTrackbar( “Position”,
“Example3”, &g_slider_position, frames, onTrackbarSlide ); } IplImage* frame; // While loop (as in Example 2) capture & show video … // Release memory và destroy window … return(0); }
V ề b ản ch ất, sau đó, chi ến thu ật này là thêm m ột bi ến toàn c ục đ ể bi ểu di ễn v ị trí slider và sau đí thêm một callback mà cập nh ật bi ến này và relocates vị trí đọc trong video. Một lời gọi tạo slider và gắn callback, và ta xong và ch ạy.* Hãy quan sát chi ti ết. int g_slider_position = 0; CvCapture* g_capture = NULL;
Đầu tiên ta định nghĩa biến toàn cục cho v ị trí slider. Callback s ẽ c ần truy c ập đ ến capture object, do đó ta đưa ra cho biến toàn cục. Vì ta tốt với mọi ng ười và thích code dễ đọc và dễ hiểu, ta chấp nhập quy đ ịnh thêm m ột ti ền t ố g_ với bất kỳ biến toàn cục. void onTrackbarSlide(int pos) { cvSetCaptureProperty( g_capture, CV_CAP_PROP_POS_FRAMES, pos );
Bây giờ ta định nghĩa callback routine để được dùng khi user đ ẩy slider. Routine này s ẽ được chuyển thành một 32-bit integer, mà sẽ là vị trí slider. Lời gọi đế n cvSetCaptureProperty() là cái ta sẽ thấy thường trong tương lai, cùng v ới b ản sao cvGetCaptureProperty() của nó. Những routine này cho phép ta cấ u hình (hay query trong trường hợp sau) các thuộc tính khác của CvCapture object. Trong trường hợp này ta chuyển argument CV_CAP_PROP_POS_FRAMES, mà nhận biết rằng ta thích đặt vị trí đọc theo đơn vị của frame. (Ta có thể dùng AVI_RATIO thay vì FRAMES nếu ta muốn đặt vị trí như phần trăm của chiều dài video toàn bộ). Cu ối cùng, ta chuy ển giá tr ị m ới c ủa vị trí vào. Vì HighGUI được khai hóa cao, nó sẽ tự động handle các v ấn đ ề này nh ư khả n ăng mà frame ta đã yêu c ầu không là m ột key-frame; nó s ẽb ắt đ ầu ởkey-frame tr ước và chuyển nhanh đến frame yêu cầu không cần ta phải quan tr ọng các chi tiết. int frames = (int) cvGetCaptureProperty( g_capture, CV_CAP_PROP_FRAME_COUNT );
Như đã hứ a, ta dùng cvGetCaptureProperty() khi ta muốn query vài data từ CvCapture nhiêu frames trong video sao structure. Trong trường hợp này, ta muốn tìm thấy bao ể ệ ỉ ướ ế cho ta có th hi u ch nh slider (trong b c ti p theo). if( frames!= 0 ) { cvCreateTrackbar( “Position”, “Example3”, &g_slider_position, frames, onTrackbarSlide ); }
Chi tiết cuối đ ể tạo chính trackbar. Function cvCreateTrackbar() cho phép ta cho trackbar một label* (trong trường hợp này là Position) và để chỉ định window đ ể đặt trackbar vào. Ta sau đó cung cấp một biến mà sẽ được gắn với trackbar, value c ực đ ại c ủa trackbar, và một callback (hay NULL nếu ta không một cái) cho khi slider được di chuyển. Quan sát thấy ta không tạo trackbar nếu cvGetCaptureProperty() trả về frame count không. Đây là vì vài, phụ thuộc vào cách video đ ược encod, t ổng s ố frame s ẽ không sẵn có. Trong trường hợp này ta sẽ chỉ play movie không có cung cấp trackbar.
Đáng khi slider tạo bởi HighGUI không đủ ch ức năng như vài slider bên ngoài. D ĩ nhiên, không có lý do bạn không thể dùng windowing toolkit bạn thích thay vì HighGUI, nhưng các HighGUI tool là nhanh để thực hiện và đủ thỏa mãn ta. Cuối cùng, ta không include extra tidbit của code c ần để làm slider di chuy ển khi video play. Điều này được để như bài tập cho ng ười đọc.
A Simple Transformation
Tuyệt, bây giờ b ạn có thể dùng OpenCV đ ể t ạo video player riêng, mà s ẽ không khác nhi uềv i ớcác countless video player bên ngoài hi nệcó. Nh ng ư ta thích computer vision, và ta muốn làm gì đó. Nhi ều vision task c ơ b ản liên quan ứng d ụng c ủa các filter với một video stream. Ta sẽ thay đổi program ta đã phải làm một tác vụ đơn giản trên mỗi frame của video khi nó play. Một tác vị đặc biệt đơn giản trong làm tr ơn image, mà gi ảm hi ệu quả n ội dung thông tin của bởi convolving nó với m ột Gaussian hay kernel function t ương t ự khác. OpenCV làm các convolution như thế là dễ. Ta có thể bắt đ ầu b ởi t ạo m ột window m ới g ọi là “Example4-out”, nơi ta có thể display các k ết qu ả c ủa processing. Sau đó, sau khi ta g ọi cvShowImage() để display captured frame mới trong input window, ta có thể tính và display ảnh làm trơn trong output window. Xem Example 2-4. screen Example 2-4. Loading và sau đó làm trơn image trước khi nó được hiển thị trên #include “cv.h” #include “highgui.h” void example2_4( IplImage* image ) // tạo some windows to show the input // và output images in. // cvNamedWindow( “Example4-in” );
Example 2-4. Loading và then smoothing an image trướ c khi it is displayed on the screen (continued) cvNamedWindow( “Example4-out” ); // tạo a window to show our input image // cvShowImage( “Example4-in”, image ); // tạo an image to hold the smoothed output // IplImage* out = cvCreateImage( cvGetSize(image), IPL_DEPTH_8U, 3 ); // Do the smoothing // cvSmooth( image, out, CV_GAUSSIAN, 3, 3 ); // Show the smoothed image in the output window // cvShowImage( “Example4-out”, out ); // Be tidy // cvReleaseImage( &out ); // Wait for the user to hit a key, then clean up the windows // cvWaitKey( 0 ); cvDestroyWindow( “Example4-in” ); cvDestroyWindow( “Example4-out” ); }
Lời gọi đầu tiên đến cvShowImage() không khác với ví dụ trước. Trong lời gọi ti ếp, ta c ấp một image structure khác. Trước đây ta dựa vào cvCreateFileCapture() để cấp frame mới cho ta. Thật ra, routine đó thực sự cấp chỉ một frame và sau đó viết data đó mỗi l ần lời gọi capture được làm (do đó nó thực sự được trả cùng pointer l ỗi l ầm ta g ọi nó). Trong trường hợp này, tuy nhiên, ta muốn allocate image structure riêng mà ta có thể viết smoothed image. Argument đ ầu tiên là một CvSize structure, mà ta có thể tạo thuận lợi bởi gọi cvGetSize(image); đi ều này cho ta size của structure image hiện tại. Argument th ứ hai nói ta kiểu của data type được dùng cho mỗi channel trên mỗi pixel, và argument
cuối nhận biết số các channel. Do đó image này là ba channels (v ới 8 bits m ỗi channel) và cùng size với image . Tác v ụ smoothing chính nó ch ỉ là m ột l ời g ọi đ ến OpenCV library: ta ch ỉ đ ịnh input image, output image, phương pháp smoothing, và các parameter để smooth. Trong trường hợp này ta yêu cầu Gaussian smooth trên 3 × 3 area tâm trên mỗi pixel. Nó thực sự được phép cho output là giống như input image, và đi ều này sẽ hi ệu qu ả h ơn trong application hiện tại của ta, nh ưng ta tránh làm điều này vì nó cho ta cơ h ội để giới thiệu cvCreateImage()! Bây giờ ta có thể thấy image trong window thứ hai mới và sau đó giải phóng nó: cvReleaseImage() lấy một pointer đế n IplImage* pointer và sau đó de-allocates tất c ả memory gắn với image đó.
A Not-So-Simple Transformation
Đó là rất tốt, và ta đang học làm nhiều thứ thú vị. Trong Example 2-4 ta chọn allocate một IplImage structure mới , và trong structure mới này ta viết output của một transformation đơn. Như đã đề cập, ta có thể áp dụng transformation theo một cách mà output viết chồng lên cái ban đầu, nhưng điều này không luôn là ý t ương tốt. Cụ thể, vài operator không sản các image với cùng size, depth, và s ố các channel nh ư input image. Điển hình, ta mu ốn th ực hi ện m ột chuỗ i các operation trên vài image ban đ uầ và do đó s nả ra m tộchu iỗcác transformed image. Trong các tr ườ ng h pợnày, thường hữu ích để giới thiệu các wrapper functions đơn gi ản mà allocate c ả output image và thực hiện transformation ta thích. Lưu ý, ví dụ, vi ệc giảm image b ởi factor 2 [Rosenfeld80]. Trong OpenCV đi u ềnày đ cượhoàn thành b i function cvPyrDown(), mà ở thực hiện Gaussian smooth và sau đó xóa mọi đường khác từ image. Điều này hữu ích trong lượng lớn các vision algorithm quan trọng. Ta có thể thực hiện function đơn gi ản mô tả trong Example 2-5. Example 2-5. dùng cvPyrDown() to tạ o a new image that is half the widthvà height of the input image IplImage* doPyrDown( IplImage* in, int filter = IPL_GAUSSIAN_5x5 ){ // Best to đảm bảo input image is divisible by two. // assert( in->width%2 == 0 && in->height%2 == 0 ); IplImage* out = cvCreateImage( cvSize( in->width/2, in->height/2 ), in->depth, in->nChannels ); cvPyrDown( in, out ); return( out ); };
Lưu ý rằng ta allocate image mới bởi đọc các parameter cần từ image c ũ. Trong OpenCV, tất cả các data type quan tr ọng được thực hiện như các structure và đ ược chuyển như các structure pointer. Không có các thứ như private data trong OpenCV! Bây giờ hãy quan sát điều tương tự nhưng dính dàng nhiều hơn liên qua Canny edge detector [Canny86] (xem Example 2-6). Trong trường hợp này, edge detector phát một image mà là size đ ầy đ ủ c ủa input image nh ưng c ần ch ỉ m ột channel image đ ơn đ ể viế t. Example 2-6. The Canny edge detector writes its output to a single channel (grayscale) image IplImage* doCanny( IplImage* in, double lowThresh, double highThresh, double aperture ){ If(in->nChannels != 1)
return(0); //Canny only handles gray scale images IplImage* out = cvCreateImage( cvSize( cvGetSize( in ), IPL_DEPTH_8U, 1 ); cvCanny( in, out, lowThresh, highThresh, aperture ); return( out ); };
Điều này cho phép ta nối cùng nhau các operator khác nhau rất d ễ. Ví d ụ, n ếu ta mu ốn co image hai l nầvà thi tìm các đ ườ ng mà có trong image đ ượ c gi mả l nầhai, ta có th ể làm như trong Example 2-7. Example 2-7. Combining the pyramid down operator (twice) và the Canny subroutine in a đơ n giản image pipeline IplImage* img1 = doPyrDown( in, IPL_GAUSSIAN_5x5 ); IplImage* img2 = doPyrDown( img1, IPL_GAUSSIAN_5x5 ); IplImage* img3 = doCanny( img2, 10, 100, 3 ); // do whatever with‘img3’ // … cvReleaseImage( &img1 ); cvReleaseImage( &img2 ); cvReleaseImage( &img3 );
Điều quan trọng để quan sát rằng chum lời gọi với các b ước khác nhau c ủa đường filtering không là ý tưởng tốt, vì sau đó ta sẽ không có cách để giải thóng các image mà ta cấp cùng với cách này. Nếu ta quá lười để làm vi ệc d ọn d ẹp này, ta có th ể ch ọn include dòng sau trong mỗi của các wrapper: cvReleaseImage( &in );
Cơ chế “tự làm sạch” này r ất g ọn, nh ưng nó sẽ có bất l ợi sau: n ếu ta th ực s ự mu ốn làm gì đó cới cái trong các image trung gian, ta ph ải truy c ập vào nó. Để giải quy ết v ấn đ ề đó, code trước có thể được đơn giản như mô tả trong Example 2-8. Example 2-8. Đơn giản đườ ng image của Example 2-7 bở i làm các bước release riêng các memory allocation trung gian IplImage* out; out = doPyrDown( in, IPL_GAUSSIAN_5x5 ); out = doPyrDown( out, IPL_GAUSSIAN_5x5 ); out = doCanny( out, 10, 100, 3 ); // do whatever with‘out’ // … cvReleaseImage ( &out );
Một từ cuối của cảnh báo đường filter tự làm sách: trong OpenCV ta phải luôn luôn chắc rằng image (hay structure khác) được de-allocated là cái, th ật ra, đ ược c ấp rõ ràng trước đây. Quan sát trường hợp của IplImage* pointer returned bởi cvCreateFileCapture(). Ở đây pointer trỏ đến một structure được cấp như một phần của CvCapture structure, và structure đích được cấp phát chỉ m ột khi khi CvCapture được khởi tạo và một AVI được tải. De-allocating structure này b ằng m ột l ời g ọi đ ến cvRelease Image() sẽ có kết quả trong vài ngạc nhiên khó ch ịu. Tinh th ần c ủa chuy ện này là, mặc dù nó quan trọng để chăm sóc garbage collection trong OpenCV, ta nên ch ỉ dọn dẹp rác mà ta đã tạo.
Input from a Camera
Vision có thể làm nhi ều thứ trong thế giới c ủa các computer. Trong vài tr ường hợp ta phân tích các frame liên tục tải từ đâu đó. Trong các tr ường hợp khác ta phân tích video mà đọc từ đĩa. Trong các trường hợp khác nữa, ta muốn làm việc với data streaming thời gian thực từ vài kiểu camera device. OpenCV—đặc biệt hơn, phần HighGUI của OpenCV library—cung cấp cách dễ đ ể handle trường hợp này. Phương pháp tương tự với cách ta đọc các AVI. Thay vì g ọi
cvCreateFileCapture(), ta gọi cvCreateCameraCapture(). Routine sau không nhận một file
name mà là camera ID number như argument của nó. Dĩ nhiên, điều này là quan trọng chỉ khi nhiều cameras sẵn có. Value mặc định là –1, mà có ngh ĩa “ch ỉ l ấy m ột”; t ự nhiên, đi ều này là r ất d ễkhi ch ỉcó m ột camera đ ểl ấy (xem ch ương 4 chi ti ết h ơn). cvCreateCameraCapture() function trả về cùng CvCapture* pointer, mà ta có thể sau đó dùng chính xác như ta làm với các frame l ấy từ một video stream. D ĩ nhiên, nhi ều vi ệc diễn ra ở hậu trường để làm một chuỗi các camera image trông như một video, nhưng ta bị ngăn với tất cả điều đó. Ta có th ể đ ơn gi ản l ấy các image t ừ camera b ất c ứ khi nào ta sẵn sàng cho chúng và tiến hành như nếu ta không biết sự khác bi ệt. Cho các nguyên nhân phát triển, hầu hết ứng dụng mà được tập trung làm việc real time s ẽ có một video-in mode, và sự phổ biến c ủa CvCapture structure làm đi ều này đ ặc bi ệt d ễ thực hiện. xem Example 2-9. Example 2-9. sau khi the capture structure is initialized, it no longer matters có hay không the image is from a camera hay a file CvCapture* capture; if( argc==1 ) { capture = cvCreateCameraCapture(0); } else { capture = cvCreateFileCapture( argv[1] ); } assert( capture != NULL ); // Rest of program proceeds totally ignorant …
Như bạn có thể thấy, arrangement này là rất lý tưởng.
Writing to an AVI File
Trong nhi uềapplications ta s mu ẽ nốrecord streaming input hay ngay c các ả captured image khác hẳn thành một output video stream, và OpenCV cung cấp một method dễ dàng để làm điều này. Chỉ như ta có thể tạo một capture device mà cho phép ta l ấy các frame ở một thời điểm từ video stream, ta có thể tạo một writer device mà cho phép ta đặt các frame từng cái một vào video file. Routine mà cho phép ta làm đi ều này là cvCreateVideoWriter(). Một khi lời gọi này được làm, ta có th ể gọi liên ti ếp cvWriteFrame() , cho mỗi frame, và cuố i cùng cvReleaseVideoWriter() khi ta xong. Example 2-10 mô tả một program đơn giản mà mở một video file, đọc nội dung, chuyển chúng thành m ột logpolar format (thứ gì đó mắt bạn thực sự thấy, được mô tả trong chương 6), và vi ết ra log-polar image vào một video file mới. Example 2-10. A complete program to read in a color video và write out the same video in grayscale // Convert a video to grayscale // argv[1]: input video file // argv[2]: name of new output file // #include “cv.h” #include “highgui.h” main( int argc, char* argv[] ) { CvCapture* capture = 0; capture = cvCreateFileCapture( argv[1] ); if(!capture){ return -1; } IplImage *bgr_frame=cvQueryFrame(capture);//Init the video read double fps = cvGetCaptureProperty ( capture, CV_CAP_PROP_FPS ); CvSize size = cvSize( (int)cvGetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH),
(int)cvGetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT) ); CvVideoWriter *writer = cvCreateVideoWriter( argv[2], CV_FOURCC(‘M’,‘J’,‘P’,‘G’), fps, size ); IplImage* logpolar_frame = cvCreateImage( size, IPL_DEPTH_8U, 3 ); while( (bgr_frame=cvQueryFrame(capture)) != NULL ) { cvLogPolar( bgr_frame, logpolar_frame, cvPoint2D32f(bgr_frame->width/2, bgr_frame->height/2), 40, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS ); cvWriteFrame( writer, logpolar_frame ); } cvReleaseVideoWriter( &writer ); cvReleaseImage( &logpolar_frame ); cvReleaseCapture( &capture ); return(0); }
Nhìn toàn bộ program này lộ ra các phần tử quen thuộc. Ta mở một video; bắt đầu đọc bằng cvQueryFrame(), mà c ần đ ểđ ọc các th ưộc tính video trên vài system; và sau đó dùng cvGetCaptureProperty() để chắc chắn các thuộc tính quan tr ọng khác c ủa video stream. Ta sau đó mở một video file để viết, convert frame thành logpolar format, và viết các frame vào file mới này từng cái đến khi không còn cái nào. Sau đó ta đóng l ại. Lời gọi đế n cvCreateVideoWriter() chứ a vài parameter mà ta nên hiểu. Đầu tiên chỉ là filename cho file mới. Thứ hai là video codec mà video stream sẽ được nén. Có vo số các codec hiện hành, nhưng codec mà bạn chọn phải sẵn có trên máy b ạn (codecs được cài độc lập với OpenCV). Trong tr ường hợp của ta ta chọn MJPG codec tương đố i phổ biế n; this is indicated to OpenCV by dùng the macro CV_FOURCC(), mà takes four characters as arguments. nhữ ng cái này characters constitute the “four-character code” of the codec, và every codec has such a code. The four-character code for motion jpeg is MJPG, so ta chỉ đ ịnh that as CV_FOURCC(‘M’,‘J’,‘P’,‘G’). The next hai arguments are the replay frame rate, và the size of the images ta s ẽ be using. In our case, ta set nhữ ng cái này to the values ta got from the original (color) video.
Onward
Tr ước khi chuy ển đ ến ch ương ti ếp theo, ta nên dành ít th ời gian đ ể l ấy stock n ơi ta ở và chuẩn bị tiến lên. Ta đã th ấy r ằng OpenCV API cung c ấp ta m ột l ượng các công c ụ dễ dùng để tải still image t ừ files, đọc video t ừ disk, hay capturing video t ừ cameras. Ta c ũng thấy r ằng library này ch ứa các primitive function đ ể thao tác nh ững images này. Điều ta chưa thấy là các ph ần tử mạnh m ẽ của library, mà cho phép thao tác phức tạp hơn của tập toàn bộ các abstract data types mà là quan trọng để giải quyết vấn đ ền vision th ực t ế. Trong vài ch ương ti ếp ta s ẽ đào sâu h ơn vào c ơ b ản và tìm hi ểu chi ti ết h ơn c ả v ề các interface-related function và image data types. Ta s ẽ xem xét các primitive image manipulation operator và, sau đó, vài thứ cao cấp hơn nhiều. Sau đó, ta sẽ sẵn sàng khám phá nhi ều services đ ặc bi ệt mà API cung c ấp cho các nhi ệm v ụkhác nh ưcamera calibration, tracking, và recognition. S ẵ sàng chưa? Đi thôi!
Exercises
Download và install OpenCV nếu bạn hi ện ch ưa có. Duy ệt hệ th ống qua directory structure. Lưu ý cụ thể docs directory; nơi bạn có thể load index.htm, mà các link xa hơn đến documentation chính của library. Khám phá xa hơn các vùng chính c ủa library.
Cvcore chứ a các data structures và algorithms cơ bản, cv chứ a image processing và vision algorithms, ml bao gồm algorithms cho machine learning và clustering, và otherlibs/highgui chứ a the I/O functions. Check out the _make directory (containing the OpenCV build files) và also các sample directory, where example code được lư u. 1. Go to the …/opencv/_make directory. On Windows, open the solution file opencv .sln; on Linux, open the appropriate makefile. Build the library in c ả the debug and the release versions. This có thể take some time, but bạn s ẽ need k ết qu ảing library và dll files. 2. Go to the …/opencv/samples/c/ directory. tạo a project hay make file and then import và build lkdemo.c (this is một ví d ụ motion tracking program). Attach a camera to your system và chạy the code. With the display window selected, type “r” to initialize tracking. B ạn có th ể add points by clicking on video positions with the mouse. Bạn có thể also switch to watching only the points (and not the image) by typing “n”. Typing “n” again s ẽ toggle giữ a “night” và “day” views. 3. dùng the capture và store code in Example 2-10, cùng nhau with the doPyrDown() code of Example 2-5 to tạo a program that reads from a camera và stores downsampled color images to disk. Modify the code in exercise 3 và combine it with4. the window display code in Example 2-1 to display the frames as they are processed. 5. Modify the program of exercise 4 witha slider control from Example 2-3 sao cho the user có thể dynamically vary the pyramid downsampling reduction level by factors of giữa 2 và 8. Bạn có thể skip writing this to disk, but b ạn should display the results.