最近在尝试用rust写视频处理代码,用到了opencv-rust这个库,这儿记录下安装过程。另外这个库说明文档比较欠缺,有些opencv接口不容易找到rust对应的调用名称或者方式,这儿将之前整理的接口查找的方法汇总了下。

1.windows下安装

1.1 llvm

这个是动态生成代码需要用到,安装好就行

https://releases.llvm.org/download.html

1.2 opencv

下载opencv安装即可,我安装的是opencv4.4版本。opencv-rust github主页用的choco安装,这个并不是必须的,可以手动安装llvm和opencv,设置环境变量就能正常的用opencv-rust

下面是官网给的环境变量设置方式,照着做就行了

opencv numpy对应版本_视频处理

上面这个图片来源于这个issue,如果遇到更多的问题可以参考下

https://github.com/twistedfall/opencv-rust/issues/118

下面是我安装的opencv和配置(与上面步骤一样),可以参考下

安装完opencv需要设置环境变量即可,本机用的vs2019,这个是vc14(设置错了编译提示出错,最后有编译命令,可以看到是vc14.xxx)

opencv numpy对应版本_视频处理_02

1.3 toml配置

opencv = {version = “0.46”, default-features = false, features = [“opencv-4”, “buildtime-bindgen”]}

前面安装llvm是为了动态生成opencv绑定用的,如果不用动态绑定编译可能会出错(本来不想装llvm,去掉了buildtime-bindgen提示编译出错)

cargo build即可使用opencv-rust了

2. opencv-rust接口使用

2.1 toml配置

需要安装llvm和opencv,这个按照前面步骤来即可

opencv = {version = “0.46”, default-features = false, features = [“opencv-4”, “buildtime-bindgen”]}

2.2 代码的位置

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才能生成

2.3 opencv的c接口

这部分代码主要在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,具体继续往下看。

2.4 rust绑定接口

这是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也有一部分

2.5 调用接口对应方法

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接口

2.6 部分重要的代码结构与类型

  • 目录结构
    此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<O> {
	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<NulError> 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<T, E = Error> = ::std::result::Result<T, E>;
  • 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;

2.7 常用接口

opencv-0.46.0\examples\ 这个目录里有一些简单的例子,更多的例子可以参考 opencv-0.46.0\tests\这个目录,

2.7.1 Mat

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<core::Mat> {
	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*> 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<cv::Mat*>(ret);
	} OCVRS_CATCH(OCVRS_TYPE(Result<cv::Mat*>))
}

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++的方式去操作即可,不用担心内存泄露

2.7.2 cv::resize

这个不要与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(…)