最近在尝试用rust写视频处理代码,用到了opencv-rust这个库,这儿记录下安装过程。另外这个库说明文档比较欠缺,有些opencv接口不容易找到rust对应的调用名称或者方式,这儿将之前整理的接口查找的方法汇总了下。
这个是动态生成代码需要用到,安装好就行
https://releases.llvm.org/download.html
下载opencv安装即可,我安装的是opencv4.4版本。opencv-rust github主页用的choco安装,这个并不是必须的,可以手动安装llvm和opencv,设置环境变量就能正常的用opencv-rust
下面是官网给的环境变量设置方式,照着做就行了
上面这个图片来源于这个issue,如果遇到更多的问题可以参考下
https://github.com/twistedfall/opencv-rust/issues/118
下面是我安装的opencv和配置(与上面步骤一样),可以参考下
安装完opencv需要设置环境变量即可,本机用的vs2019,这个是vc14(设置错了编译提示出错,最后有编译命令,可以看到是vc14.xxx)
opencv = {version = “0.46”, default-features = false, features = [“opencv-4”, “buildtime-bindgen”]}
前面安装llvm是为了动态生成opencv绑定用的,如果不用动态绑定编译可能会出错(本来不想装llvm,去掉了buildtime-bindgen提示编译出错)
cargo build即可使用opencv-rust了
需要安装llvm和opencv,这个按照前面步骤来即可
opencv = {version = “0.46”, default-features = false, features = [“opencv-4”, “buildtime-bindgen”]}
cargo下载的代码都在下面这个目录:
C:\Users\xxx\.cargo\registry\src\github.com-yyyy\
xxx是电脑的用户名,github.com-yyyy每个人可能不一样,也可能会有几个文件夹,找到最新的一个文件夹。
这个目录下面找到:opencv-0.46.0这个目录,opencv-rust的代码都在这儿
部分binding代码需要在用到opencv-rust的工程里cargo build才能生成
这部分代码主要在opencv-0.46.0\bindings\cpp\opencv_4目录里
这部分是c代码,是opencv-rust这个库用llvm生成的。由于rust只能调用c接口,这儿的c代码将rust无法直接调用的代码封装成c接口。比如:cpp成员函数调用\函数重载\namespace等c没有的东西
c接口的Result定义
定义在opencv-0.46.0\src_cpp\ocvrs_common.hpp 这个文件里,这个文件主要是c部分的Result和OCVRS_CATCH的定义,这两个基本上所有c接口函数都用到。而c里的Result会通过Rust里对应的Result的into_result转换成Rust内部的std::result::Result,具体继续往下看。
这是rust代码使用的opencv的接口
opencv-0.46.0\src\opencv目录下默认是opencv4,内容与opencv-0.46.0\bindings\rust\opencv_4是一样的,库编译的时候用的是src目录的内容
sys.rs是 2.3里的c接口的extern声明,这个基本上不需要看,函数名/接口与opencv的c接口一样的
常用代码在:core.rs,imgproc.rs。imgcodecs.rs和highgui.rs也有一部分
opencv的cpp接口在rust里对应的接口以及调用参数查找方法
首先在opencv-0.46.0\bindings\cpp\opencv_4目录找到core.cpp\imgproc.cpp,看下c++下对应的接口被封装在了哪个c接口里
比如:cv::cvtColor,这个在imgproc.cpp里
Result_void cv_cvtColor_const__InputArrayR_const__OutputArrayR_int_int(const cv::_InputArray* src, const cv::_OutputArray* dst, int code, int dstCn) {
try {
cv::cvtColor(*src, *dst, code, dstCn);
return Ok();
} OCVRS_CATCH(OCVRS_TYPE(Result_void))
}
其中:对应的c的函数封装后的名对应的是:
cv_cvtColor_const__InputArrayR_const__OutputArrayR_int_int
用这个c的函数名称去opencv-0.46.0\src\opencv\hub目录下同名的文件去找,这儿对应的是:imgproc.rs
搜索c的函数名可以看到如下的rust封装代码,rust对外提供的接口名是cvt_color这个名字。可以看到opencv在Rust里的函数名称与c/c++的有区别,这就是需要按上面方法找对应关系的原因。
pub fn cvt_color(src: &dyn core::ToInputArray, dst: &mut dyn core::ToOutputArray, code: i32, dst_cn: i32) -> Result<()> {
input_array_arg!(src);
output_array_arg!(dst);
unsafe { sys::cv_cvtColor_const__InputArrayR_const__OutputArrayR_int_int(src.as_raw__InputArray(), dst.as_raw__OutputArray(), code, dst_cn) }.into_result()
}
可以看到这儿unsafe里调用的是sys::xxxx这种形式,前面提到过外部c接口
目录结构
此crate的入口在src/lib.rs,所有的mod都在src目录下,这个注意下就行了,opencv-0.46.0\bindings\rust\opencv_4这个目录并不在这个crate里,这个只是生成的一个中间目录
Result
c结构部分定义了Result模板(见前面c接口部分),注意这个是c里的Result结构体,与Rust里的std::result::Result不是一样的。从上面代码段的调用可以看到,c接口返回的Result通过into_result转换成了Rust的Result。opencv-0.46.0\src\manual\sys.rs有这个c接口里Result的定义,两种类型的Result转换在Result定义的下方,具体见下面代码:
pub fn into_result(self) -> CrateResult {
if self.error_msg.is_null() {
Ok(self.result.into())
} else {
Err(Error::new(self.error_code, unsafe { crate::templ::receive_string(self.error_msg as *mut String) }))
}
}
Rust的Result在crate::Result,从lib.rs里可以看出这个Result在 error::Result
打开src\error.rs看到Result的定义
#[derive(Debug)]
pub struct Error {
pub code: i32,
pub message: String,
}
impl Error {
pub fn new(code: i32, message: String) -> Self {
Self { code, message }
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} (code: {})", self.message, self.code)
}
}
impl From for Error {
fn from(_: NulError) -> Self {
Self::new(core::StsBadArg, "Passed Rust string contains nul byte".into())
}
}
impl std::error::Error for Error {}
pub type Result = ::std::result::Result;
crate::core
在代码里经常看到crate::core这个模块,下面看下这个模块的位置
在lib.rs的第五行可以看到如下代码引入了hub的所有模块
pub use crate::opencv::hub::*;
其中hub模块引入了core,imgcodecs等模块,后面代码里出现的crate::core等模块都是指hub里的这些模块
pub mod calib3d;
pub mod core;
pub mod dnn;
pub mod features2d;
pub mod flann;
pub mod highgui;
pub mod imgcodecs;
pub mod imgproc;
pub mod ml;
pub mod objdetect;
pub mod photo;
pub mod stitching;
pub mod video;
pub mod videoio;
#[cfg(feature = "contrib")]
pub mod world;
pub mod types;
opencv-0.46.0\examples\ 这个目录里有一些简单的例子,更多的例子可以参考 opencv-0.46.0\tests\这个目录,
Mat在下面这个文件里定义:
opencv-0.46.0\src\opencv\hub\core.rs
Mat的接口除了impl Mat还有MatTrait定义的方法,分别搜索impl Mat和pub trait MatTrait可以看下有没有要用的接口
元素访问
一维接口: at
二维接口: at_2d
Mat内存释放
Mat的内存管理与c++是否一致?
比如ROI区域操作,opencv-rust里roi定义如下
pub fn roi(m: &core::Mat, roi: core::Rect) -> Result {
unsafe { sys::cv_Mat_Mat_const_MatR_const_RectR(m.as_raw_Mat(), &roi) }.into_result().map(|r| unsafe { core::Mat::opencv_from_extern(r) } )
}
这儿用的是:cv_Mat_Mat_const_MatR_const_RectR,这个在c++里定义如下,可以看到这儿用的是new cv::Mat,也就是roi函数new了一个Mat对象
Result cv_Mat_Mat_const_MatR_const_RectR(const cv::Mat* m, const cv::Rect* roi) {
try {
cv::Mat* ret = new cv::Mat(*m, *roi);
return Ok(ret);
} OCVRS_CATCH(OCVRS_TYPE(Result))
}
Mat生命周期结束后会自动调用drop,drop代码在 opencv-0.46.0\src\opencv\hub\core.rs 里
可以看到drop里调用了 cv_Mat_delete函数
impl Drop for Mat {
fn drop(&mut self) {
extern "C" { fn cv_Mat_delete(instance: *mut c_void); }
unsafe { cv_Mat_delete(self.as_raw_mut_Mat()) };
}
}
cv_Mat_delete函数在opencv-0.46.0\bindings\cpp\opencv_4\core.cpp里定义,这儿只是简单的调用了delete释放了Mat对象,delete 会引发自动执行Mat的析构函数
void cv_Mat_delete(cv::Mat* instance) {
delete instance;
}
c++的Mat对象的析构函数可以看下官方文档:https://docs.opencv.org/4.4.0/d3/d63/classcv_1_1Mat.html
这个网页里搜索:~Mat 可以看到文档里只有一句:calls release
继续看下release函数,这个函数会减少引用计数,到0了就真正的释放内存
opencv-rust实际上用的是opencv c++的内存管理方法,Rust只是多了一个Mat::drop触发析构函数的步骤而已,使用过程中按照c++的方式去操作即可,不用担心内存泄露
这个不要与Mat::resize弄混了
搜索的方法:在opencv-rust库里搜索所有文件:cv::resize
在imgproc.cpp里有调用
Result_void cv_resize_const__InputArrayR_const__OutputArrayR_Size_double_double_int(const cv::_InputArray* src, const cv::_OutputArray* dst, cv::Size* dsize, double fx, double fy, int interpolation) {
try {
cv::resize(*src, *dst, *dsize, fx, fy, interpolation);
return Ok();
} OCVRS_CATCH(OCVRS_TYPE(Result_void))
}
可以看到封装后的c接口为:
cv_resize_const__InputArrayR_const__OutputArrayR_Size_double_double_int
在opencv-rust库里搜索这个c接口名,可以在imgproc.rs里找到如下代码
pub fn resize(src: &dyn core::ToInputArray, dst: &mut dyn core::ToOutputArray, dsize: core::Size, fx: f64, fy: f64, interpolation: i32) -> Result<()> {
input_array_arg!(src);
output_array_arg!(dst);
unsafe { sys::cv_resize_const__InputArrayR_const__OutputArrayR_Size_double_double_int(src.as_raw__InputArray(), dst.as_raw__OutputArray(), dsize.opencv_as_extern(), fx, fy, interpolation) }.into_result()
}
所以找到这个cv::resize在Rust里接口为:imgproc::resize(…)