Một vài vấn đề trong lập trình với OpenCV

Ngày 05/06/2015:

Ngày 04/06/2015, sau hơn 6 tháng kể từ bản beta, phiên bản chính thức OpenCV 3.0.0 đã được đưa ra với rất nhiều cải tiến. Bên cạnh đó bản TBB 4.3 update 5 cũng được MS cập nhật. Boost 1.5.8 cũng mới được cập nhật trong thời gian gần đây.

Ngày 13/11/2014:

Bản OpenCV 3.0-Beta đã được đưa ra sau nhiều cải tiến. Bên cạnh đó bản cập nhật IPP 8.2.1 và TBB 4.3.1 cũng được cập nhật. Microsoft cũng đã tung ra bản update 4 cho Visual Studio 2013 và bản Preview cho VS 2015. Boost 1.5.7 cũng mới được cập nhật trong thời gian gần đây. Qủa là một ngày với nhiều cập nhật.

Ngày 22/08/2014:

Với rất nhiều các tính năng mới và một giao diện (API) C++ gần như triệt để (các hàm hỗ trợ C với tiếp đầu ngữ cv, chẳng hạn như cvCopyImage, cvFillImage đã bị loại bỏ và số còn lại thì ngày càng ít đi), bản OpenCV 3.0 Alpha đã được đưa ra. Có thể xem thêm về các tính năng mới của bản alpha này ở đây và ở đây.  Link download.

Ngày 16/08/2014:

Bản OpenCV 3.0.0 dev-version mới nhất với các tính năng sau:

+ Hỗ trợ OpenCL mạnh hơn (nhiều hàm được cài đặt với OpenCL kernel hơn). Nền tảng OpenCL được mặc định sử dụng, nếu hệ thống không hỗ trợ, có thể có báo lỗi.

+ Nhanh hơn với thư viện IPP của Intel, hầu hếtcác thao tác đều nhanh hơn (xem chi tiết ở đây), chưa phát hiện hàm nào chậm hơn.

+ Module highgui được chia thành 2: videoio and highgui.

+ Sửa lỗi khi build module cudaoptflow.

+ Cập nhật với bản cmake 3.

+ Một số applications bị bỏ đi, chỉ còn một application là traincascade.

Ngày 21/07/2014:

Một địa chỉ với nhiều ví dụ theo kiểu “Learn by examples” về OpenCV: http://opencvexamples.blogspot.com/.

TBB 4.2 update 5.

Ngày 3/03/2014:

Forum cho các hỏi đáp về các vấn đề về lập trình với OpenCV (thực sự rất có ích): OpenCV Q&A forum.

Ngày 13/01/2014:

Update 3 of TBB 4.2: Hôm nay bản Update 3 của thư viện TBB 4.2 đã được tung ra. Đối với những ai muốn tối ưu tốc độ của thư viện OpenCV, thì TBB là một giải pháp khá tốt, bên cạnh các giải pháp khác như SSE2, WinThread hoặc C++11 thread. Có thể download bản TBB 4.2 update 3 ở địa chỉ này, xem các thay đổi từ bản Update 2 lên Update 3 ở đây.

Ngày 07/12/2013:

Một số nguồn, tài liệu có ích cho những người mới bắt đầu với OpenCV.

+ OpenCV 2.4.8 reference manual. Tài liệu về version mới nhất của OpenCV giúp tra cứu các hàm thư viện của OpenCV theo từng mục tương ứng với các file thư viện khác nhau như: core, imgproc, highgui, video …

+ OpenCV Tutorial 2.4.3: http://docs.opencv.org/opencv_tutorials.pdf. Hướng dẫn chi tiết về cấu hình với công cụ (VS, Eclipse), nền tảng (Windows, Linux, Adroid, iOS) và ngôn ngữ khác nhau (C++, Java). Thêm vào đó là khá nhiều ví dụ đơn giản, ngắn gọn.

http://opencv-srf.blogspot.fr/. Một Tutorial khác, cũng khá dễ hiểu.

Ngày 28/08/2013:

GUI application with OpenCV: để tạo một ứng dụng giao diện đồ họa sử dụng OpenCV có một số lựa chọn: dùng MFC, Qt, hoặc wxWidgets. Tuy nhiên Qt chiếm ưu thế hơn vì:

+ MFC đang bị MS đưa vào dạng cần loại bỏ

+ MFC chỉ chạy với Windows

+ wxWidgets không tiện và mạnh như Qt

+ Ứng dụng Qt có thể dễ dàng compile lại để chạy trên các hệ thống Windows hoặc Linux

Một số ví dụ cơ bản về tạo ứng dụng GUI sử dụng Qt với OpenCV:

link 1 – Hiển thị ảnh đơn giản

link 2 – Đa luồng và một số thao tác lọc ảnh

link 3 – Thao tác với webcam

Ngày 03/06/2013:

Xử lý ảnh xám (grayscale image processing). Theo mặc định khi hàm imread() hoặc cvLoadImage() đọc 1 ảnh (từ file hoặc một frame video), ảnh nhận được (Mat hoặc IplImage) sẽ có 3 channel cho ba màu Blue, Green, Red (theo đúng thứ tự). Tuy nhiên việc xử lý ảnh thường dựa trên ảnh xám (một channel, với giá trị điểm ảnh, hay cường độ sáng – intensity, dao động từ 0 tới 255) nên điều quan trọng đầu tiên là cần chuyển một ảnh đọc được về ảnh xám 1 channel, để thực hiện điều này ta làm như sau:

Mat im = imread(<file path>);
if(im.type()!=CV_8UC1)
	cvtColor(im, im, CV_BGR2GRAY);

Tiếp đến do các thao tác xử lý dựa trên điểm ảnh cần làm việc với các giá trị kiểu số thực nên ta cần chuyển tiếp các điểm ảnh về kiểu CV_32F hoặc CV_64F như sau:

im.convertTo(im,CV_32F);

Chú ý là thao tác này (chuyển thành ảnh xám 1 channel) là rất quan trọng vì nhiều hàm xử lý của OpenCV chỉ làm việc với dữ liệu ảnh (Mat) 1 channel, chẳng hạn như minMaxLoc(), compare().

Tất nhiên ta cũng có thể tách các channel của một ảnh hoặc hợp lại từ 3 channel bằng các hàm split() và merge(), ví dụ:

Mat im = imread(<file path>);
vector<Mat> rgb; 
split(in, rgb);

Mat color;

split(rgb, color);

Để truy cập vào một điểm ảnh ở hàng i cột j ta cùng cú pháp: im.at<float>(i,j) cho ảnh CV_32F và  im.at<double>(i,j) cho ảnh CV_64F.

Lấy giá trị mức xám lớn nhất (max) và nhỏ nhất (min) của một ảnh:

Mat im=imread(<file path>);

if(im.type()!=CV_8UC1)
	cvtColor(im, im, CV_BGR2GRAY);
double max_val, min_val; 
minMaxIdx(im, &min_val, &max_val, 0, 0); 
cout << "Max=" << max_val << ", Min=" << min_val << endl;

Ngày 30/05/2013:

1. Đọc/ghi file ảnh. Dữ liệu cho một chương trình xử lý ảnh là ảnh thu nhận từ các thiết bị ghi, thường được lưu dưới dạng file ảnh hoặc file video với các format khác nhau. Về cơ bản, OpenCV hỗ trợ đọc hầu hết các format ảnh phổ biến (.png, .jpg, .bmp, …), còn về format video thì trên hệ thống có codec nào, OpenCV có thể đọc được format đó, tuy nhiên phổ biến nhất là các file .avi. Hàm đọc file ảnh có 2 loại, một dành cho chương trình viết theo dạng C (sử dụng cấu trúc IplImage) và loại thứ hai dành cho các chương trình viết theo kiểu C++ (dùng lớp Mat), cụ thể như sau:

IplImage * img = cvLoadImage(<file path>);

Mat img = imread(<file path>);

Tương ứng là hai hàm dùng để ghi file ảnh:

cvSaveImage(<file path>, img ); // cho IplImage * img;

imwrite(<file path>, img); // cho Mat img;

Để đọc các frame của một file video hoặc từ một webcam có thể dùng cấu trúc CvCapture (khi đó ảnh đọc sẽ có kiểu là IplImage) hoặc VideoCapture (khi đó ảnh đọc sẽ là đối tượng của lớp Mat). Nói chung OpenCV không phân biệt ảnh được đọc từ file hay trực tiếp từ webcam (Bravo OpenCV!). Ban đầu là thao tác khởi tạo:

CvCapture* capture=0;
IplImage* frame=0;

capture = cvCaptureFromAVI(<file path>); // from file

capture = cvCaptureFromCAM(0); // from webcam

// đọc các frame tiếp theo

frame = cvQueryFrame( capture );

VideoCapture capture;

Mat frame;

capture.open(<file path>); // open file

capture.open(0); // open webcam device

// đọc các frame

capture >> frame;

Sau đây là một ví dụ đơn giản về đọc file .avi và hiển thị lên màn hình:

#include <opencv2\opencv.hpp>
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <opencv2\imgproc.hpp>
using namespace cv;
int main(int argc, char** argv)
{
	CvCapture* capture=0;
	IplImage* frame=0;
	capture = cvCaptureFromAVI(argv[1]); // read AVI video
	if( !capture )
		throw "Error when reading steam_avi";

	cvNamedWindow( "w", 1);
	for(;;)
	{
		frame = cvQueryFrame( capture );
		if(!frame)
			break;
		cvShowImage("w", frame);

		cvWaitKey(10);
		if(cvWaitKey(30) >= 0)
			break;
	}
	cvReleaseImage(&frame);
	cvReleaseCapture( &capture );
	cvDestroyWindow("w");

	return 0;
}

Còn đây là một ví dụ với C++:

#include <opencv2\opencv.hpp>
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <opencv2\highgui\highgui_c.h>
#include <opencv2\imgproc\imgproc_c.h>
//#include <iostream>
//using namespace std;
using namespace cv;
int main(int argc, char ** argv)
{
	VideoCapture capture;
	Mat frame;
	capture.open(argv[1]);
	if(!capture.isOpened())
		return 1;
	while(1)
	{
		capture >> frame;
		if(frame.empty())
			break;
		imshow("w", frame);
		cvWaitKey(10);
		if(cvWaitKey(30) >= 0)
			break;
	}
	return 0;
}

Xem thêm các ví dụ và chi tiết khác tại:

http://www.grandmaster.nu/blog/?page_id=49

http://docs.opencv.org/doc/tutorials/highgui/video-write/video-write.html

http://bsd-noobz.com/opencv-guide/40-4-display-video-from-file-camera

http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html

Ngày 23/05/2013:

1. Chuyển đổi kiểu giữa IplImage * (kết quả đọc ảnh bằng hàm cvLoadImage(), là một cấu trúc theo kiểu C) và Mat (một kiểu dữ liệu mới từ OpenCV 2, kết quả của hàm imread()) dùng hàm cvarrToMat():

IplImage * img = cvLoadImage(<file path>);

Mat mat = cvarrToMat(img);

cvShowImage(“Using IplImage”, img);

imshow(“Using Mat”, mat);

Kiểu dữ liệu Mat của OpenCV (thực chất là một class của C++) hay được dùng vì có nhiều thao tác dựa trên cấu trúc ma trận của nó. Để chuyển ngược lại thì đơn giản hơn:

Mat mat = imread(<file path>);

IplImage img = mat;

2. OpenCV và lập trình GPU

Để có thể lập trình GPU với thư viện OpenCV, ta cần hai file thư viện opencv_gpu (.lib và .dll). Do bản chính thức là 2.4.5 dành cho Windows không được dịch với GPU đầy đủ nên không chạy được các hàm GPU của OpenCV, khi đó cần build OpenCV from source để có thể dùng các hàm này. Xem hướng dẫn ở đây với bản OpenCV 2.4.9.

3. GpuMat và Mat

GpuMat là cấu trúc tương tự Mat nhưng được dùng để chạy trên GPU Device, ví dụ khi muốn đọc ảnh, chuyển dữ liệu ảnh lên GPU để thực hiện thao tác xử lý gì đó (lọc ảnh, trích chọn đặc trưng, DFT, FFT, Wavelets, convolution ..) ta dùng hàm upload, rồi gọi tới hàm xử lý, tiếp đến dùng hàm download để chuyển dữ liệu từ GPU device xuống Host Memory:

Mat img = imread(<file path>);

Mat result;

GpuMat d_img;

d_img.upload(img);

<gọi hàm trên GPU để xử lý ảnh>

d_img.download(result);

CUDA 5.0 with Visual C++ 2010 Express-The very first program

This post is my sharing about how to config CUDA 5.0 (exactly 5.0.35) with Visual C++ Express 2010 on Windows 7. Besides, some other issues are mentioned including how to compile a CUDA program, how to measure runtime of a function or part of code, how to make Visual C++ and Visual Assist X aware of CUDA C++ code, and the last thing is the answer to the questtion: ” Is it possible to program (write code, compile only) on a non CUDA machine?”.

1. Installation

You need 2 program, Visual C++ 2010 Express and CUDA 5 (32 bit or 64 bit based on your system). After downloading them, install the Visual C++ first, then the CUDA library (choose all the options). There is nothing special about this step.

2. Write your first program

The files of a CUDA program are classified as two types: the normal C++ source file (*.cpp and *.h, ect.) and the CUDA C++ file (*.cu and *.cuh). The CUDA source file must be compiled by NVCC program (a compiler from Nvidia) and the resulted binary code will be combined with the code from the normal C++ file, which is compiled by VS C++ compiler. So the problem is that how to make this compilation run smoothly. And here are steps of writing a CUDA program:

+ Open VS C++ 2010 Express.

+ File->New->Project->Empty Project, enter the name of the project, Exp1.

+ In the Solution Explorer Tab, add new source file for your project, choose the C++ File (.cpp) type and type the name of the file as main.cu.

config include6

+ Write your code:

/**
* A matrix multiplication using the cuBLAS library.
*/

#include <cstdlib>
#include <iostream>
#include <string>

#include <time.h>

#include <cublas.h>

typedef float ScalarT;

// Some helper functions //
/**
* Calculates 1D index from row-major order to column-major order.
*/
#define index(r,c,rows) (((c)*(rows))+(r))

#define CudaSafeCall( err ) __cudaSafeCall( err, __FILE__, __LINE__ )
inline void __cudaSafeCall( cublasStatus err, const char *file, const int line )
{
if( err != CUBLAS_STATUS_SUCCESS )
{
std::cerr << “CUDA call failed at ” << file << “:” << line << std::endl;
exit (EXIT_FAILURE);
}
}

#define AllocCheck( err ) __allocCheck( err, __FILE__, __LINE__ )
inline void __allocCheck( void* err, const char *file, const int line )
{
if( err == 0 )
{
std::cerr << “Allocation failed at ” << file << “:” << line << std::endl;
exit (EXIT_FAILURE);
}
}

void printMat( const ScalarT* const mat, size_t rows, size_t columns, std::string prefix = “Matrix:” )
{
// Maximum to print
const size_t max_rows = 5;
const size_t max_columns = 16;

std::cout << prefix << std::endl;
for( size_t r = 0; r < rows && r < max_rows; ++r )
{
for( size_t c = 0; c < columns && c < max_columns; ++c )
{
std::cout << mat[index(r,c,rows)] << ” “;
}
std::cout << std::endl;
}
}
// Main program //
int main( int argc, char** argv )
{
size_t HA = 4200;
size_t WA = 23000;
size_t WB = 1300;
size_t HB = WA;
size_t WC = WB;
size_t HC = HA;

size_t r, c;

cudaEvent_t tAllStart, tAllEnd;
cudaEvent_t tKernelStart, tKernelEnd;
float time;

// Prepare host memory and input data //
ScalarT* A = ( ScalarT* )malloc( HA * WA * sizeof(ScalarT) );
AllocCheck( A );
ScalarT* B = ( ScalarT* )malloc( HB * WB * sizeof(ScalarT) );
AllocCheck( B );
ScalarT* C = ( ScalarT* )malloc( HC * WC * sizeof(ScalarT) );
AllocCheck( C );

for( r = 0; r < HA; r++ )
{
for( c = 0; c < WA; c++ )
{
A[index(r,c,HA)] = ( ScalarT )index(r,c,HA);
}
}

for( r = 0; r < HB; r++ )
{
for( c = 0; c < WB; c++ )
{
B[index(r,c,HB)] = ( ScalarT )index(r,c,HB);
}
}

// Initialize cuBLAS //

cublasStatus status;
cublasInit();

// Prepare device memory //
ScalarT* dev_A;
ScalarT* dev_B;
ScalarT* dev_C;

status = cublasAlloc( HA * WA, sizeof(ScalarT), ( void** )&dev_A );
CudaSafeCall( status );

status = cublasAlloc( HB * WB, sizeof(ScalarT), ( void** )&dev_B );
CudaSafeCall( status );

status = cublasAlloc( HC * WC, sizeof(ScalarT), ( void** )&dev_C );
CudaSafeCall( status );

cudaEventCreate(&tAllStart);
cudaEventCreate(&tAllEnd);
cudaEventRecord(tAllStart, 0);

status = cublasSetMatrix( HA, WA, sizeof(ScalarT), A, HA, dev_A, HA );
CudaSafeCall( status );

status = cublasSetMatrix( HB, WB, sizeof(ScalarT), B, HB, dev_B, HB );
CudaSafeCall( status );

// Call cuBLAS function //
cudaEventCreate(&tKernelStart);
cudaEventCreate(&tKernelEnd);
cudaEventRecord(tKernelStart, 0);

// Use of cuBLAS constant CUBLAS_OP_N produces a runtime error!
const char CUBLAS_OP_N = ‘n’; // ‘n’ indicates that the matrices are non-transposed.
cublasSgemm( CUBLAS_OP_N, CUBLAS_OP_N, HA, WB, WA, 1, dev_A, HA, dev_B, HB, 0, dev_C, HC ); // call for float
// cublasDgemm( CUBLAS_OP_N, CUBLAS_OP_N, HA, WB, WA, 1, dev_A, HA, dev_B, HB, 0, dev_C, HC ); // call for double
status = cublasGetError();
CudaSafeCall( status );

cudaEventRecord(tKernelEnd, 0);
cudaEventSynchronize(tKernelEnd);

cudaEventElapsedTime(&time, tKernelStart, tKernelEnd);
std::cout << “time (kernel only): ” << time << “ms” << std::endl;

// Load result from device //
cublasGetMatrix( HC, WC, sizeof(ScalarT), dev_C, HC, C, HC );
CudaSafeCall( status );

cudaEventRecord(tAllEnd, 0);
cudaEventSynchronize(tAllEnd);

cudaEventElapsedTime(&time, tAllStart, tAllEnd);

std::cout << “time (incl. data transfer): ” << time << “ms” << std::endl;

// Print result //
//printMat( A, HA, WA, “\nMatrix A:” );
//printMat( B, HB, WB, “\nMatrix B:” );
//printMat( C, HC, WC, “\nMatrix C:” );

// Free CUDA memory //
status = cublasFree( dev_A );
CudaSafeCall( status );

status = cublasFree( dev_B );
CudaSafeCall( status );

status = cublasFree( dev_C );
CudaSafeCall( status );

status = cublasShutdown();
CudaSafeCall( status );

// Free host memory //
free( A );
free( B );
free( C );

return EXIT_SUCCESS;
}
+ Config the project as a CUDA project. In the Solution Explorer, right click on the name of the project and choose Build Customizations, in the dialog appeared, check the CUDA 5.0 option, then OK.

config include6
config include6

+ Right click on the CUDA code file (main.cu in this example), choose Properties. In the dialog appeared, choose CUDA C/C++ as the image below:

config include6

+ In the Property Manager tab (View->Property Manager), right click on the Microsoft.Cpp.Win32.user as the image below and choose Properties.

config include6

+ In the VC++ Directories, you have to add some paths (to folders) of CUDA include files, reference folder, library files, like in the images below (do not close the dialog after this step):

config include6
config include6
config include6

+ In the Linker tree, choose Input and add the library files needed for CUDA programs as in the image below:

config include6

+ You will be asked to save the configuration (for all CUDA programs), choose Yes. The configuration steps (start with the operations in the Property Manager above) are needed only one time.
Now you can build your program (use Release option).

3. Timing measurement

In earlier time of CUDA (version <5.0) there are two ways that can be used to measure the time of a program, a function or a part of the proram. But in CUDA 5 (or in the best of my knowledge with CUDA 5), only one way: using cudaEvent_t.

+ Declaration:

cudaEvent_t tAllStart, tAllEnd;
float time;

+ Start recording time information:

cudaEventCreate(&tAllStart);
cudaEventCreate(&tAllEnd);
cudaEventRecord(tAllStart, 0);

+ Stop recording time information:

cudaEventRecord(tAllEnd, 0);
cudaEventSynchronize(tAllEnd);

+ Get the time and output:

cudaEventElapsedTime(&time, tAllStart, tAllEnd);
std::cout << “time (incl. data transfer): ” << time << “ms” << std::endl;

4. How to make Visual C++ and Visual Assist X be aware of the CUDA source files?

You can get this information from the links below:

+ Link 1

+ Link 2

5. Is it possible to program (write code and compile only) on a non CUDA machine?

This question is related to my circumstance because I have one CUDA desktop machine at the lab, which can be remoted controlled from my house, so I would like to write and compile the program on my labtop, then copy the program file to the desktop machine to run. Fortunately, the question is YES. We can write and compile CUDA program on a non CUDA machine. You install the Visual tool first, then the CUDA toolkit but do not select the CUDA driver option since your machine does not have any CUDA device. The same steps should be followed with the laptop for getting things done.

Your comments on this topic are welcome!