Rust 和 OpenCV
我们都知道为什么 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)
Linux 用户通常足够聪明,可以弄清楚如何在他们的机器上安装 OpenCV,否则请按照此处的指南进行操作:https://github.com/twistedfall/opencv-rust
Windows 用户可以按照本指南进行操作:https://github.com/twistedfall/opencv-rust
对于 Mac 用户,你可以按照下面的超短教程进行操作。
让我们从安装 OpenCV 开始。不幸的是,OpenCV 不是另一个 Rust 包。它需要在你的计算机上安装 OpenCV (C++)。
然而,在 Rust 中,不需要痛苦的链接和编写 CMake 文件。Rust 中的 OpenCV 实际上比使用 C++ 更容易,并且当你想要引入许多依赖项(大量 CMake 文件的 gulp)时不会让你头疼。
在 macOS 上安装它非常容易。假设你有 brew,那么只需要运行:
brew install opencv
然后在你的 cargo.toml 添加
[dependencies]
opencv =
"0.63.0"# or whatever version is the latest你可以按照 opencv-rust 存储库获取完整的安装帮助:https://github.com/twistedfall/opencv-rust
当安装它时,在编译时遇到了问题,但可以按照故障排除部分轻松修复。因此,如果你遇到问题,请确保在抓头发之前检查该部分。
这个 OpenCV Rust 绑定到 C++ API(这很好,因为 C 已经被废弃了)。
由于 Rust 可以直接与 C 接口,C++ 被包装在一个额外的 C 层中,然后暴露给 Rust。
简单代码
第一个示例将基于 Makeitnow 的视频教程:
https://www.youtube.com/watch?v=zcfixnuJFXg
对于有经验的 OpenCV 用户来说,这非常简单。
使用 anyhow 来处理结果:https://docs.rs/anyhow/latest/anyhow/
所以将使用它而不是 opencv::Result。
让我们写代码吧!
anyhow::
Result;
// Automatically handle the error typesuse
opencv::{
prelude::*,
videoio,
highgui
};
// Note, the namespace of OpenCV is changed (to better or worse). It is no longer one enormous.fnmain
() ->
Result<()> {
// Note, this is anyhow::Result// Open a GUI window
highgui::named_window(
"window", highgui::WINDOW_FULLSCREEN)?;
// Open the web-camera (assuming you have one)
letmut
cam = videoio::VideoCapture::new(
0, videoio::CAP_ANY)?;
letmut
frame = Mat::default();
// This array will store the web-cam data// Read the camera
// and display in the window
loop
{
cam.read(&
mut frame)?;
highgui::imshow(
"window", &frame)?;
let
key = highgui::wait_key(
1)?;
if
key ==
113 {
// quit with qbreak
;
}
}
Ok
(())
}
太棒了!我们可以打开一个网络摄像头并将生成的帧放入 frame 变量中。
代码应该是不言自明的。否则看视频!
PS:这将是等效的 Python 代码
cv2
vid = cv2.VideoCapture(
0)
whileTrue
:
ret, frame = vid.read()
cv2.imshow(
'window', frame)
if
cv2.waitKey(
1) &
0xFF == ord(
'q'):
break
vid.release()
cv2.destroyAllWindows()
使用 OpenCV-Rust 绑定变得更加温暖
让我们:
从文件中读取图像 使用 SIFT & ORB 检测关键点 用不同颜色绘制关键点 画一个矩形 将图像转换为 ndarray(快速) 将 ndarray 转换为 image::RgbImage (用于测试我们上面的步骤按预期工作) 保存图片
在这里,将首先将代码作为一个块删除,然后逐步分解。
anyhow::anyhow;
use
anyhow::
Result;
use
image::RgbImage;
use
ndarray::{Array1, ArrayView1, ArrayView3};
use
opencv::{
selfas cv, prelude::*};
fnmain
() ->
Result<()> {
// Read image
let
img = cv::imgcodecs::imread(
"./assets/demo_img.png", cv::imgcodecs::IMREAD_COLOR)?;
// Use Orb
letmut
orb = <
dyn cv::features2d::ORB>::create(
500
,
1.2
,
8
,
31
,
0
,
2
,
cv::features2d::ORB_ScoreType::HARRIS_SCORE,
31
,
20
,
)?;
letmut
orb_keypoints = cv::core::Vector::default();
letmut
orb_desc = cv::core::Mat::default();
letmut
dst_img = cv::core::Mat::default();
let
mask = cv::core::Mat::default();
orb.detect_and_compute(&img, &mask, &
mut orb_keypoints, &
mut orb_desc,
false)?;
cv::features2d::draw_keypoints(
&img,
&orb_keypoints,
&
mut dst_img,
cv::core::VecN([
0.,
255.,
0.,
255.]),
cv::features2d::DrawMatchesFlags::DEFAULT,
)?;
cv::imgproc::rectangle(
&
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.,
0.]),
-
1,
cv::imgproc::LINE_8,
0
,
)?;
// Use SIFT
letmut
sift = cv::features2d::SIFT::create(
0,
3,
0.04,
10.,
1.6)?;
letmut
sift_keypoints = cv::core::Vector::default();
letmut
sift_desc = cv::core::Mat::default();
sift.detect_and_compute(&img, &mask, &
mut sift_keypoints, &
mut sift_desc,
false)?;
cv::features2d::draw_keypoints(
&dst_img.clone(),
&sift_keypoints,
&
mut dst_img,
cv::core::VecN([
0.,
0.,
255.,
255.]),
cv::features2d::DrawMatchesFlags::DEFAULT,
)?;
// Write image using OpenCV
cv::imgcodecs::imwrite(
"./tmp.png", &dst_img, &cv::core::Vector::default())?;
// Convert :: cv::core::Mat -> ndarray::ArrayView3
let
a = dst_img.try_as_array()?;
// Convert :: ndarray::ArrayView3 -> RgbImage
// Note, this require copy as RgbImage will own the data
let
test_image = array_to_image(a);
// Note, the colors will be swapped (BGR <-> RGB)
// Will need to swap the channels before
// converting to RGBImage
// But since this is only a demo that
// it indeed works to convert cv::core::Mat -> ndarray::ArrayView3
// I'll let it be
test_image.save(
"out.png")?;
Ok
(())
}
traitAsArray
{
fntry_as_array
(&
self) ->
Result<ArrayView3<
u8>>;
}
impl
AsArray
for cv::core::Mat {
fntry_as_array
(&
self) ->
Result<ArrayView3<
u8>> {
if
!
self.is_continuous() {
returnErr
(anyhow!(
"Mat is not continuous"));
}
let
bytes =
self.data_bytes()?;
let
size =
self.size()?;
let
a = ArrayView3::from_shape((size.height
asusize, size.width
asusize,
3), bytes)?;
Ok
(a)
}
}
// From Stack Overflow: https://stackoverflow.com/questions/56762026/how-to-save-ndarray-in-rust-as-image
fnarray_to_image
(arr: ArrayView3<
u8>) -> RgbImage {
assert!
(arr.is_standard_layout());
let
(height, width, _) = arr.dim();
let
raw = arr.to_slice().expect(
"Failed to extract slice from array");
RgbImage::from_raw(width
asu32, height
asu32, raw.to_vec())
.expect(
"container should have the right size for the image dimensions")
}
阅读图片
读取图像非常简单。你可能希望对所有这些图像加载成功进行检查。如果找不到图像,OpenCV 不会抛出错误。并且不要被 Rust 中的 Result 所迷惑,它不会检查图像是否正确加载。
Rust
let
img = opencv::imgcodecs::imread(
"./assets/demo_img.png", cv::imgcodecs::IMREAD_COLOR)?;
C++
cv::Mat I = cv::imread(
"./assets/demo_img.png",
0);
Python
"./assets/demo_img.png)
关键点检测与绘制
ORB 和 SIFT 代码非常相似,所以只对 ORB 部分进行评论。
所以首先创建检测器
Rust
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 的文档中找到。只需将鼠标悬停在“创建”功能上,你就会看到文档 [VSCode]。
C++ 默认参数可以在 VSCode 中看到。如果使用另一个 IDE,你可能只需转到函数定义并阅读文档字符串。
计算关键点
Rust
orb_keypoints = cv::core::Vector::default();
letmut
orb_desc = cv::core::Mat::default();
orb.detect_and_compute(&img, &mask, &
mut orb_keypoints, &
mut orb_desc,
false)?;
C++
::
vector<cv::KeyPoint> keypoints;
orbPtr->detect(image, keypoints);
cv::Mat desc;
orbPtr->compute( image, keypoints, desc );
同样,没有什么太大的差异。在开始时,可能很难知道如何初始化关键点和描述符。但是一旦看到,就很简单了。从上面的代码来看,不能说 Rust 代码比 C++ 复杂。
绘制关键点
Rust
dst_img = cv::core::Mat::default();
cv::features2d::draw_keypoints(
&img,
&orb_keypoints,
&
mut dst_img,
cv::core::VecN([
0.,
255.,
0.,
255.]),
cv::features2d::DrawMatchesFlags::DEFAULT,
)?;
C++
cv::Mat dst_img;
cv::drawKeypoints(image, keypoints, dst_img);
弄清楚 Rust 中的哪些类型有点棘手。使用类型推断和一点直觉就可以找到它!
Detective Work - 绘制矩形(一个更有指导性的步骤)
由于 OpenCV Rust 绑定几乎没有文档,因此它是一种侦探游戏。我决定展示 C++ 代码是有原因的。
我们可以看到,大部分 Rust 代码都可以从 C++ 中推断出来(在某种程度上)。利用今天令人难以置信的 IDE,我们有机会。
此外,Rust 有一个很好的文档系统。策略是依靠opencv-rust 文档(https://docs.rs/opencv/latest/opencv/index.html)和 C++ 的命名。
所以让我们使用这个策略来弄清楚如何绘制矩形。
首先,我们想好要做什么,即:
绘制矩形(在 C++ 中是 cv::rectangle
)
然后我们转到opencv-rust docs:https://docs.rs/opencv/latest/opencv/index.html
现在,我们所要做的就是找出类型(说起来容易做起来难)。在这里,我们将使用 IDE(在我的例子中是 VSCode)。使用 LSP,Nvim 或 Emacs 也应该没问题。
第一个参数应该很明显,图像。那将是 cv::core::Mat。我们可以通过直觉来弄清楚。Mat是存储图像数据的默认类型,所以应该是Mat。Mat 实现了 ToInputOutputArray 特性,一切都很好。
接下来,什么是 Rect 类型?
似乎我们可以在 core 中找到它。那很好。但是,我该如何构建一个 Rect?
使用 LSP,我们可以找到合适构造函数的自动完成。在这里,再次,有一点直觉和运气可以快速找到它(尽管与 Rust 相比,C++ 代码完成远非那么容易上手)。如果你被困在 C++ 中,通常会在网上寻找解决方案,这也不是那么令人愉快。
好的,让我们在这里看看 from_points 构造函数要说什么
我们需要two points!……但Point是什么?🥁
在 core 看起来像这样!让LSP为我们做更多的提升!
所以,“new”似乎很有希望,但“from_vec2”也是如此!
我们可能可以使用其中任何一个。但是让我们选择“new”。
让我们给它输入两个整数,看看会发生什么。
所以现在我们已经弄清楚了第二个参数。
cv::core::Rect::from_points(
cv::core::Point::new(0, 0),
cv::core::Point::new(50, 50
)
(现在其余的方法相同……)。这有点乏味,但是一旦你开始了解类型,工作就会变得更轻松。在那个阶段,感觉很自然,你不会介意用 Rust 而不是 C++ 工作。
ndarray!
ndarray 似乎是 Rust 上最适合的矩阵库(对于正在为 Python 的 NumPy 包编写绑定的小伙子或小伙子来说,ndarray 是要走的路)。后面还有一篇关于用 Python 和 NumPy 绑定 Rust 的文章!
这有点棘手。现在我们需要将 C++ 类型转换为 Rust 类型。我们知道我们正在处理矩阵类型。它们通常是行优先的:https://en.wikipedia.org/wiki/Row-_and_column-major_order
OpenCV Mat 和 ndarray Array(View) 都是行优先的。并且数据按顺序存储在底层缓冲区中。为了确保在我们的案例中,我们将检查 Mat 是否连续。
!mat.is_continuous() {
returnErr
(anyhow!(
"Mat is not continuous :("));
}
我们可以利用这些知识快速将 cv::Mat 转换为 ndarray::Array。
但是,必须注意的是,Array 将指向存储在 Mat 中的数据。因此,当 Mat 被丢弃时,Array 也必须被丢弃。否则我们(很可能)指向释放的内存,这样不好!但似乎 Rust 为我们处理了这个!
首先,我们将提取 Mat 的数据字节。由于图像存储为 8 位 (u8) 的无符号整数,我们可以直接读取数据而无需对其进行类型转换。
et data_bytes: &[
u8] = mat.data_bytes()?;
// <-- This is the image data in sequence! Note it is pointing to the data in mat接下来,我们需要弄清楚这个数据的大小。当我们获取数据字节时,我们不会以我们想要的形状获取它们,而是以一个长序列获取它们。
size = mat.size()?;
let
h:
i32 = size.height;
let
w:
i32 = size.width;
现在我们可以根据这些信息构造一个 ArrayView3<u8>
a = ArrayView3::from_shape((h
asusize, w
asusize,
3), data_bytes)?;
// The 3 is because we have bgr. For gray image this will be 1好的!我们得到了一种将 Mat 转换为 ArrayView3<u8> 并具有性能的方法。
请注意,这仅适用于连续数组。
作为一个特征,看起来像
{
fntry_as_array
(&
self) ->
Result<ArrayView3<
u8>>;
}
impl
AsArray
for cv::core::Mat {
fntry_as_array
(&
self) ->
Result<ArrayView3<
u8>> {
if
!
self.is_continuous() {
returnErr
(anyhow!(
"Mat is not continuous"));
}
let
bytes =
self.data_bytes()?;
let
size =
self.size()?;
let
a = ArrayView3::from_shape((size.height
asusize, size.width
asusize,
3), bytes)?;
Ok
(a)
}
}
为了快速将 Mat 转换为 Array,我们现在可以调用:
array: ArrayView<
u8> = mat.try_as_array()?;
结论
Rust 中的 OpenCV 肯定是可能的。它需要更深入的知识才能在不同类型之间进行转换,并且需要意志力来弄清楚绑定。但它有效!
由于 Rust 包管理器 Cargo 非常好,它鼓励使用其他人的包(例如用于在许多流行的 crate 之间转换图像类型的cv-convert(https://crates.io/crates/cv-convert)使生活更轻松。
希望我们将来会看到更多很酷的包。一些 Rust GPU 包开始出现,谁知道呢,也许将来有可能将 Rust 直接编译为 SPIR-V 以实现真正的快速计算!那将是多么美好的未来!
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。