Rust和OpenCV-600学习网
600学习网终身会员188,所有资源无秘无压缩-购买会员
我们都知道为什么Rust这么好。然而,与C/C++和其他老巨人相比,它有点太新颖和闪亮了。我们经常需要在没有适当文档的情况下使用C++绑定。
出身背景
现在,让我们先回答这个问题。为什么我们关心在Rust中运行OpenCV?为什么不直接使用C++.Java或Python?
C++是一个古老的冠军。与Rust或Go相比,编译C++代码并不有趣。对于在Python中成长的年轻一代来说,使用C++安装包似乎是中世纪的。
谁愿意花时间安装软件包?特别是今天,有这么多优秀而有权势的人。Rust的包管理器Cargo很棒。
在Python中使用OpenCV很容易。易于在大型社区中安装和使用。如果你真的想把工作做好,Python是最好的选择。虽然Python语言非常慢,但实际上,很少有代码行是Python代码的主要特姓。
如果你只是想做一些需要for循环的额外函数呢?或者你想并行运行这些东西?Python可以做到这一点,但使用起来并不容易。
Rust的OpenCV
入门-安装(MacOS)
对于Mac用户,您可以遵循下面的简短教程。
让我们从安装OpenCV开始。不幸的是,OpenCV不是另一个Rust包。它要求在您的计算机上安装OpenCV(C++)。然而,在Rust中,没有必要痛苦地链接和编写CMake文件。Rust中的OpenCV实际上比C++更容易,当你想引入许多依赖项(大量的CMake文件)时,它不会让你头疼。
它很容易在macOS上安装。如果你有brew,你只需要运行:
brew安装opencv
然后加上你的货物。汤姆勒
〔依赖关系〕
opencv=”0.63.0″#或任何最新版本
您可以根据opencv信任存储库获得完整的安装帮助:
在安装它时,我们在编译过程中遇到了问题,但我们可以根据故障排除部分轻松修复它们。所以,如果你有问题,一定要在抓头发之前检查一下这个部位。
这个OpenCV Rust绑定到了C++API(这很好,因为C已经被放弃了)。
由于Rust可以直接与C接口,所以C++封装在一个额外的C层中,然后暴露于Rust。
简单代码
第一个例子是一个基于Makeitnow的视频教程:
这对于经验丰富的OpenCV用户来说非常简单。
使用任意方式处理结果:
因此将使用它来代替opencv::Result。
让我们来写代码!
无论如何使用::结果;//自动处理错误类型
使用opencv::{
序曲::*
视频
高gui
}; // 请注意,OpenCV的名称空间已更改(更好或更糟)。它不再是一个巨大的名称空间。
fn main()->结果<()>{//注意,无论如何::结果
//打开GUI窗口
highgui::命名为_window(“window”,highgui::window_FULLSCREEN)
//打开网络摄像机(假设你有一台)
让mut cam=videoio::VideoCapture::new(0,videoio::CAP _ ANY)
let mut frame=Mat::default();//此数组将存储网络摄像头数据
//读相机
//并在窗口中显示
循环{
cam.read(&mut frame)
highgui::imshow(“窗口”,&frame)
let key=highgui::wait_key(1)
如果键==113{//用q退出
打破
}
}
确定(())
}
太棒了!我们可以打开网络摄像头并将生成的帧放入帧变量。
代码应该是不言自明的。否则请观看视频!
PS:这将是等效的Python代码
导入cv2
vid=cv2.VideoCapture(0)
当为True时:
ret,帧=vid.read()
cv2.imshow(“窗口”,框架)
如果cv2.waitKey(1)和0xFF==ord(‘q’):
打破
视频发布()
cv2.销毁所有Windows()
使用OpenCV–Rust绑定变得更热
让我们:
·从文件读取图像
·使用SIFT&ORB检测关键点
·使用不同颜涩绘制关键点
·绘制矩形
·将图像转换为数组(快速)
·将数组转换为image::RgbImage(用于测试上述步骤是否按预期工作)
·保存图片
在这里,首先将代码作为一个块删除,然后逐步分解。
使用anyhow::anyhow
无论如何使用::Result
使用image::RgbImage
使用ndarray::{Array1,ArrayView1,Array View3}
使用opencv::{self作为cv,preparde::*}
fn main()->结果<()>{
//读取图像
让img=cv::imgcodecs::imread(“./assets/demo_img.png”,cv:;imgcodecs::imread_COLOR)
//使用球
让mut orb=<dyn cv::features2d::orb>::create(
500,
1.2,
8.
31,
0
2.
简历::features2d::ORB_分数类型::哈里斯_分数
31,
20,
)?;
让mut orb_关键点=cv::core::Vector::default()
让mut orb_desc=cv::core::Mat::default()
让mut dst_img=cv::core::Mat::default()
let mask=cv::core::Mat::default()
orb.检测和计算(&img,&mask,&mut orb_关键点,&mut-orb_desc,false)
cv::features2d::绘制_个关键点(
&img中
&orb_个关键点
&mut dst_ img
cv::core::VecN([0.,255.,0.,255.])
cv::features2d::DrawMatchesFlags::默认
)?;
cv::imgproc::矩形(
&mut dst_ img
cv::core::Rect::from_points(cv::core::Point::new(0,0),cv:core::Point::new(50,50))
cv::core::VecN([255.,0.,0.])
-1,
cv::imgproc::LINE_8
0
)?;
//使用SIFT
让mut sift=cv::features2d::sift::create(0,3,0.04,10.,1.6)
让mut sift_关键点=cv::core::Vector::default()
让mut sift_desc=cv::core::Mat::default()
sift。检测和计算(&img,&mask,&mut-sift,关键点,&mut sift,desc,false)
cv::features2d::绘制_个关键点(
&dst_img.clone()
筛选关键点
&mut dst_ img
cv::core::VecN([0.,0.,255.,255.])
cv::features2d::DrawMatchesFlags::默认
)?;
//使用OpenCV写入图像
cv::imgcodecs::imwrite(“./tmp.png”,&dst_img,&cv:核心::矢量::默认())
//转换::cv::core::Mat->ndarray::ArrayView3
设a=dst_img。try_as_array()
//转换::ndarray::ArrayView3->RgbImage
//注意,这需要拷贝,因为RgbImage将拥有数据
让test _ image=数组_到_ image(a)
//注意,颜涩将互换(BGR<->RGB)
//之前需要交换频道
//转换为RGBImage
//但由于这只是一个演示
//它确实可以转换cv::core::Mat->ndarray::ArrayView3
//我会听其自然
test_image.save(“out.png”)
确定(())
}
trait AsArray{
fn try_as_array(&self)->结果<ArrayView3<u8>>
}
cv::core::Mat的impl AsArray{
fn try_as_array(&self)->结果<ArrayView3<u8>>{
如果自我是连续的
return Err(无论如何!(“Mat不连续”)
}
let bytes=self.data_bytes()
let size=self.size()
让a=ArrayView3::from_shape((size.height as usize,size.width as usiz,3),bytes)
好的(a)
}
}
//来自堆栈溢出:https://stackoverflow.com/问题/56762026/如何-保存-ndarray-in-rust-as-image
fn数组_到_图像(arr:ArrayView3<u8>)->RgbImage{
明确肯定(arr.is_标准_布局())
let(高度,宽度,_)=arr.dim()
let raw=arr.to_slice().expect(“未能从数组中提取切片”)
RgbImage::从__raw(宽度为u32,高度为u32)到__vec()
.expect(“容器的尺寸应与图像尺寸相符”)
}
阅读图片
阅读图像非常简单。您可能需要检查所有这些图像是否已成功加载。如果找不到图像,OpenCV将不会抛出错误。不要被Rust的结果弄糊涂了。它不会检查图像是否正确加载。
锈
//读取图像
让img=opencv::imgcodecs::imread(“./assets/demo_img.png”,cv:imgcodecs::imread_COLOR)
C++
cv::Mat I=cv:imread(“./assets/demo_img.png”,0)
蟒蛇
img:np.ndarray=cv2.imread(“./assets/demo_img.png)
关键点检测和绘制
ORB和SIFT代码非常相似,因此只对ORB部分进行注释。因此,首先创建检测器Rustlet mut orb=<dyn cv::features2d::orb>:create(500,1.2,8,31,0,2,cv:,features2d::orb_ScoreType::HARRIS_SCORE,31,20,)?;
C++
cv::Ptr<cv::ORB>orbPtr=cv:;ORB::create()
这与C++非常相似。需要提供一些不同的名称空间和参数。请注意,所有默认变量都可以在Rust的文档中找到。只需将鼠标悬停在”Create”函数上,您就会看到文档[VSCode]。
在VSCode中可以看到C++默认参数。如果您使用另一个IDE,您可以简单地转到函数定义并读取文档字符串。
计算关键点
锈
让mut orb_关键点=cv::core::Vector::default()让mut orb_desc=cv::core::Mat::default()orb.检测和计算(&img,&mask,&mut orb_关键点,&mut-orb_desc,false)
C++
std::vector<cv::KeyPoint>关键点
orbPtr->检测(图像.关键点)
cv::材料描述
orbPtr->计算(图像.关键点.描述)
再说一遍,没有太大区别。一开始,可能很难知道如何初始化密钥和描述符。但一旦你看到了它,就很容易了。从上面的代码来看,我们不能说Rust代码比C++更复杂。
绘制关键点
锈
让mut dst_img=cv::core::Mat::default()
cv::features2d::绘制_个关键点(&img,&orb_keypoints,&mut dst _img,cv:核心::VecN([0.,255.,0.,255.]),cv::feature 2d::DrawMatchesFlags::默认,)
C++
cv::材料dst _ img
cv::drawKeypoints(图像,关键点,dst_img)
弄清楚Rust的类型有点棘手。使用类型推断和一点直觉来找到它!
侦探工作-画一个矩形(更具启发姓的步骤)
由于OpenCV Rust绑定的文档很少,所以它是一款侦探游戏。我决定展示C++代码是有原因的。
我们可以看到,大多数Rust代码都可以从C++中推断出来(在某种程度上)。有了今天令人难以置信的IDE,我们就有了机会。
此外,Rust有一个很好的文档系统。该策略取决于opencv信任文档
以及C++。因此,让我们使用这个策略来计算如何绘制矩形。
首先,我们应该弄清楚该做什么,即:
·绘制一个矩形(cv::C++中的矩形),然后转到opencv信任文档
现在,我们要做的就是找出类型(说起来容易做起来难)。在这里,我们将使用IDE(在我的例子中是VSCode)。使用LSP.Nvim或Emacs也可以。
第一个参数应该很明显,图像。这将是cv::core::Mat。我们可以通过直觉把它弄清楚。Mat是存储图像数据的默认类型,因此应该是Mat。Mat已经实现了ToInputOutputArray功能,一切都很好。
接下来,什么是Rect类型?
看来我们可以在核心找到它。那太好了。然而,我怎样才能建造一个Rect?
使用LSP,我们可以找到适当构造函数的自动完成。在这里,一点直觉和运气可以很快找到它(尽管与Rust相比,C++代码的完成远不容易掌握)。如果你被困在C++中,你通常会在网上找到解决方案,这不是很愉快。
好的,让我们看看from_points构造函数在这里说什么
我们需要两点但什么是点?????
核心看起来像这样!让LSP为我们做更多!
因此,”new”看起来很有前途,但”from_vec2″也是如此!我们可以使用其中的任何一个。但让我们选择”新”。
让我们在其中输入两个整数,看看会发生什么。现在我们已经找到了第二个参数
cv::core::Rect::from_points(
cv::core::Point::new(0,0)
cv::core::Point::new(50,50
)
(现在其他方法相同…)。这有点无聊,但一旦你开始了解类型,工作就会变得容易。在那个阶段,感觉很自然。您不会介意使用Rust而不是C++。
ndarray
看起来darray是Rust上最合适的矩阵库(对于为Python的NumPy包编写绑定的人来说,darray就是最好的选择)。还有一篇关于用Python和NumPy绑定Rust的文章!
这有点棘手。现在我们需要将C++类型转换为Rust类型。我们知道我们在处理矩阵类型。它们通常是第一行:https://en。维基百科。org/wiki/Row-_and_column-major_order
OpenCV Mat和数组数组(View)是行优先的。数据按顺序存储在底层缓冲区中。为了确保在我们的情况下,我们将检查Mat是否连续。
如果材料是连续的
return Err(无论如何!(“Mat不连续:(“))
}
我们可以使用这些知识将cv::Mat快速转换为array::array。
但是,必须注意,数组将指向Mat中存储的数据。因此,当Mat被丢弃时,数组也必须被丢弃。否则,我们(可能)指向释放的内存,这是糟糕的!但看起来Rust为我们处理了这件事!
首先,我们将提取Mat的数据字节。由于图像存储为8位(u8)无符号整数,我们可以直接读取数据而无需类型转换。
et data字节:&[u8]=mat.data字节()?;//<-这是按顺序排列的图像数据注意,它指向mat中的数据
接下来,我们需要计算出这些数据的大小。当我们得到数据字节时,我们并没有得到我们想要的形状,而是按照一个长的顺序。
let size=mat.size()
设h:i32=尺寸高度
设w:i32=size.width
现在我们可以构造ArrayView3
设a=ArrayView3::来自_形状((h为usize,w为usiz,3),数据为_字节)?;//3是因为我们有bgr。对于灰度图像,这将是1
good我们已经获得了一种将Mat转换为ArrayView3<u8>的方法。
请注意,这仅适用于连续数组。
作为一个功能,它看起来像
trait AsArray{
fn try_as_array(&self)->结果<ArrayView3<u8>>
}
cv::core::Mat的impl AsArray{
fn try_as_array(&self)->结果<ArrayView3<u8>>{
如果自我是连续的
return Err(无论如何!(“Mat不连续”)
}
let bytes=self.data_bytes()
let size=self.size()
让a=ArrayView3::from_shape((size.height as usize,size.width as usiz,3),bytes)
好的(a)
}}
要快速将Mat转换为Array,我们现在可以调用:
let array:ArrayView<u8>=mat.try_as_array()
结论
Rust中的OpenCV必须是可能的。它需要更深入的知识才能在不同类型之间进行转换,并且需要意志力来确定绑定。但它起作用了!
因为Rust包管理器Cargo非常好,它鼓励使用其他人的包(例如cv convert(https://crates.io/crates/cv转换),用于在许多流行的板条箱之间转换图像类型)。
我希望我们将来能看到更多的酷包。一些Rust GPU包已经开始出现。谁知道,将来有可能将Rust直接编译到SPIR-V中,以实现真正的快速计算!那将是多么美好的未来啊!
600学习网 » Rust和OpenCV-600学习网