Bạn thích viết 1 project bằng C# vì nó dễ thiết kế giao diện, các API thân thiện, dễ sử dụng. Tuy nhiên OpenCV không hỗ trợ C#, do đó để có thể sử dụng các tính năng trong OpenCV có nhiều phương pháp, phổ biến nhất hiện nay là dùng EmguCV. EmguCV là thư viện wrapper chuyển đổi tất cả các hàm của openCV từ C++ sang .NET. Tuy nhiên nó có nhiều nhược điểm: tốc độ xử lý chậm, bắt buộc người dùng phải include tất cả file dll dung lượng lớn.
Để khắc phục vấn đề trên thì sử dụng c++ để viết các hàm tính toán sử dụng openCV, dùng C# để thiết kế giao diện và CLR (common language runtime) để làm cầu nối cho 2 project đó. Như vậy chương trình sẽ có ưu điểm:
- Dễ thiết kế giao diện, dễ tùy biến
- Tốc độ xử lý nhanh
- Dung lượng nhỏ
Nhược điểm là hơi lằng nhằng, phức tạp và khó debug.
Các bước thực hiện:
1. Tạo project C++ rồi dùng project này làm lib
2. Tạo project Windows Form bằng C# để làm giao diện
3. Tạo 1 project kết nối 2 project trên
Project chính dùng để tạo UI viết bằng C#, build ra file .exe
Project opencv viết bằng C++ để xử lý hình ảnh, build ra file .lib
Project trung gian viết bằng CLR để gọi hàm từ C# qua C++, build ra file .dll
Bước 1: Tạo project C++ để làm lib
Các bạn nên sử dụng Visual Studio 2017 để code, không nên sử dụng các phiên bản khác (2015 thì cũ, 2019 thì không ổn định)
Các bạn làm theo bài viết Setup project OpenCV cho ngôn ngữ C++ để tạo project opencv. Project này sẽ xử lý hình ảnh, trả về kết quả là hình ảnh xử lý hoặc giá trị nhận diện được, hay nói cách khác là core của chương trình.
Theo bài viết trên thì output là file exe để thực thi, bạn phải chuyển thành file .lib ở Configuration
Bước 2: Tạo project CLR để kết nối 2 project
CLR là viết tắt của Common Language Runtime, bạn có thể hiểu nó là 1 ngôn ngữ hơi lai giữa C# và C++ (Giải thích như vậy không đúng với định nghĩa, chỉ là giúp bạn dễ hình dung)
Tạo project clr
Cấu hình để project CLR build ra file dll mà .NET dùng được. Các bạn nhớ config giống trong hình, các bước include lib làm giống như bài tạo project C++
Bước 3: Tạo project C#
Tạo project C# với dạng Windows Form, sau đó add reference tới project CLR. Ấn vào project C# -> Refernces -> Add Reference…
Sau đó chọn project CLR mà bạn muốn reference tới. (Sau nhiều lần thay đổi thì opencvdotnet đã đổi thành TGMTbridge)
Kiểm tra: Tiếp theo là viết vài dòng code đơn giản để kiểm tra chạy thành công hay chưa:
- Bên project c++ viết hàm để trả về 1 Mat (VD: load ảnh và chuyển thành ảnh xám)
- Bên project clr viết hàm convert Mat và trả về bitmap
- Bên project C# tạo 1 Picture box và hiển thị bitmap nhận được
- libjpeg-turbo: dùng để xử lý ảnh jpg
- libpng: dùng để xử lý ảnh png
- zlib: nén/giải nén data, dùng cho 2 project trên
- opencv_core: project chính của opencv xử lý các phép tính toán mat (matrix – ma trận)
- opencv_highgui: tạo form hiển thị Mat, các control như trackbar, button,…
- opencv_imgcodecs: load ảnh bằng cách gọi các lib libjpeg-turbo, >libpng
- opencv_imgproc: các phép biến đổi hình ảnh bằng phép tính toán ma trận
- Sample: project xử lý ảnh viết bằng C++ build ra file lib
- UI: project C# để làm giao diện
- TGMTbridge: cầu nối giữa UI và Sample
Để tránh code rườm rà khó hiểu mình đưa toàn bộ code vào project mẫu. Các bạn down về chạy thử, nếu xuất ra hình ảnh là ok.
Tham khảo: Convert Bitmap sang Mat và ngược lại
C# sử dụng định dạng hình ảnh bitmap, còn OpenCV là Mat, do đó cần có hàm để convert Mat thành Bitmap
[bg_collapse view=”button-orange” color=”#4a4949″ expand_text=”Show More” collapse_text=”Show Less” ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Bitmap^ TGMTbridge::MatToBitmap(cv::Mat img) { if (img.data == nullptr) return nullptr; if (img.type() != CV_8UC3) { throw gcnew NotSupportedException("Only images of type CV_8UC3 are supported for conversion to Bitmap"); } //create the bitmap and get the pointer to the data Bitmap ^bmpimg = gcnew Bitmap(img.cols, img.rows, PixelFormat::Format24bppRgb); BitmapData ^data = bmpimg->LockBits(System::Drawing::Rectangle(0, 0, img.cols, img.rows), ImageLockMode::WriteOnly, PixelFormat::Format24bppRgb); byte *dstData = reinterpret_cast<byte*>(data->Scan0.ToPointer()); unsigned char *srcData = img.data; for (int row = 0; row < data->Height; ++row) { memcpy(reinterpret_cast<void*>(&dstData[row*data->Stride]), reinterpret_cast<void*>(&srcData[row*img.step]), img.cols*img.channels()); } bmpimg->UnlockBits(data); delete(data); img.release(); return bmpimg; } |
Còn đây là hàm convert Bitmap thành Mat
[bg_collapse view=”button-orange” color=”#4a4949″ expand_text=”Show More” collapse_text=”Show Less” ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
cv::Mat BitmapToMat(Bitmap^ bitmap) { Drawing::Rectangle rect = Drawing::Rectangle(0, 0, bitmap->Width, bitmap->Height); BitmapData^ bmpData = bitmap->LockBits(rect, ImageLockMode::ReadWrite, bitmap->PixelFormat); // data = scan0 is a pointer to our memory block. IntPtr data = bmpData->Scan0; // step = stride = amount of bytes for a single line of the image size_t step = bmpData->Stride; // So you can try to get you Mat instance like this: cv::Mat mat; if (bitmap->PixelFormat == PixelFormat::Format8bppIndexed) { mat = cv::Mat(bitmap->Height, bitmap->Width, CV_8UC1, data.ToPointer(), step); } else if (bitmap->PixelFormat == PixelFormat::Format24bppRgb) { mat = cv::Mat(bitmap->Height, bitmap->Width, CV_8UC3, data.ToPointer(), step); } else { TGMTbridgeUtil::ShowErrorBox("IPSS error", "Does not support input image because don't know format"); } // Unlock the bits. bitmap->UnlockBits(bmpData); delete(bmpData); return mat; } |
Download
Nếu các bước trên phức tạp đối với bạn hoặc gặp lỗi khó giải quyết thì sử dụng source có sẵn. Mình có viết 1 sample đơn giản để minh họa việc gọi C++ từ C#. Trong repo Github có nhiều project sample, các bạn chạy riêng CsCallCpp.sln để hiểu cách C# gọi C++;
Solution CsCallCpp.sln load 1 ảnh rồi làm mờ bằng thuật toán blur(). Nhớ là clone submodule mới build được chương trình.
Trong Solution có nhiều project:
Lưu ý: khi Visual Studio 2017 hỏi có retarget các project không thì các bạn cứ chọn cancel
Quá tuyệt vời. Ad thêm giải thích code nữa cho dễ hiểu dễ học
Chào bạn, những bài viết của bạn ở trang này rất tuyệt.
Nhưng mình vẫn không hiểu cái lib ở bước 1 có liên quan gì đến việc tạo dll ở bước thứ hai không? Mong bạn giúp mình hiểu. Mình cảm ơn. (Mình xin lỗi vì đọc trên github của bạn mình thấy rất rối, không biết chỗ nào là tạo lib, chỗ nào là tạo dll, và chỗ nào là kết nối dll với project C#. Nếu bạn giúp mình hiểu được thì tốt cho mình quá).
Hi bạn, để làm theo project cần tới 3 project. Project C++ build ra file .lib, project CLR build ra .dll còn C# là .exe
Do OpenCV viết bằng C++ nên project xử lý thuật toán là project C++, mà C++ thì rất khó làm UI nên viết UI bằng C#, để kết nối 2 project lại với nhau ta sử dụng CLR.
Cảm ơn bạn đã trả lời mình, mình đã hiểu nguyên tắc này. Cho mình hỏi thêm rằng: trong phần code mẫu trên Github của bạn thì cái nào là project để tạo lib file, cái nào là project tạo dll file, và cái nào là project C# đã sử dụng dll file kia?
Mình thấy có project CsCallCpp.sln, nhưng khi load vào VS2019 community thì nó chỉ load được 1 project là UI (các project còn lại như libpng, zlib,…là không load được). Ngoài ra, trong mục reference của project UI thì bị missing nhưng thư viện như AForge.Video, AForge.Video.DirectShow và IPSSBridge (mình không hiểu chúng lấy từ đâu ra?). Mong bạn xem giúp.
– Về source code đúng là ở mode x64 build ra bị lỗi, mình đã fix rồi. Bạn pull code mới về là được hoặc chuyển sang mode x86 cũng được.
– Project Sample để xử lý ảnh, project UI để tạo giao diện và TGMTbridge để làm cầu nối giữa 2 project
Cảm ơn bạn đã hồi đáp mình. Những bài viết của bạn rất hay. Mình sẽ lưu lại làm tài liệu tham khảo cho bản thân mình khi làm việc với lĩnh vực này. Một lần nữa rất cảm ơn bạn đã hỗ trợ mình nhiều như vậy. Chúc bạn luôn mạnh khỏe và thành công hơn nữa trong lĩnh vực theo đuổi của bạn.
ad có thể hướng dẫn chi tiết hơn bước 3 được không, mk chưa biết cách add reference tới project CLR
Mình đã thêm hình để minh họa ở bước 3 rồi nhé
cảm ơn ad nhiều
em đang bị 1 lỗi này mong ad giúp
em đang tạo 1 cái event click từ button khi ta click vào button thì sẽ gọi ra lệnh videoCatupre để bật camera ở máy tính
lỗi của em gặp ở đây là chỉ khi ta ấn vào nút click thì cái camera mới được bật còn không thì nó sẽ , mong ad có thể gợi ý em cách để cho camera vẫn giữ nguyên trạng thái bật.
private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
VideoCapture cap;//mo camera mac dinh
Mat frame;
cap.open(0);
if (!cap.isOpened()) {
MessageBox::Show(“Khong mo duoc Camera”, “Thông báo”, MessageBoxButtons::OK, MessageBoxIcon::Error);
}
else {
// Khai bao bien luu anh
Mat src_hsi;
Mat frame;
cap >> frame;
if (frame.empty()) {
MessageBox::Show(” Khong nhan duoc anh “, “Thông báo”, MessageBoxButtons::OK, MessageBoxIcon::Error);
}
cvtColor(frame, src_hsi, COLOR_BGR2HSV);
Bitmap^ bmp = Mat2Bitmap(frame);
this->pictureBox1->Image = bmp;
}
}
Bạn đưa VideoCapture cap; ra ngoài function, sau đó tạo vòng lặp while để đọc ảnh liên tục.
Cho em hỏi là tại sao khi em add Reference project clr vào bên c# project thì Reference Manager lại không tìm ra được project nào ? Lỗi này fix sao ạ em xin cảm ơn
Bạn để ý xem có vào các trường hợp này không:
1. Phải chọn vào group “Projects” để hiện ra các project trong solution
2. Solution chỉ có 1 project
Nếu không phải thì vào group https://www.facebook.com/groups/thigiacmaytinh post hình lên xem thử