折腾笔记[10]-使用rust进行ORB角点检测

news/2025/2/28 20:18:26/文章来源:https://www.cnblogs.com/qsbye/p/18686371

摘要

打包ORB算法到bye_orb_rs库,使用rust进行ORB角点检测.
Package the ORB algorithm into the bye_orb_rs library, and use Rust for ORB corner detection.

关键词

rust;ORB;FAST;slam;

关键信息

项目地址:[https://github.com/ByeIO/slambook2.rs]

[package]
name = "exp65-rust-ziglang-slambook2"
version = "0.1.0"
edition = "2021"[dependencies]
env_logger = { version = "0.11.6", default-features = false, features = ["auto-color","humantime",
] }# 随机数
rand = "0.8.5"
rand_distr = "0.4.3"
fastrand = "2.3.0"# 线性代数
nalgebra = { version = "0.33.2",features = ["rand"]}
ndarray = "0.16.1"# winit
wgpu = "23.0.1"
winit = "0.30.8"# egui
eframe = "0.30.0"
egui = { version = "0.30.0", features = ["default"
]}
egui_extras = {version = "0.30.0",features = ["default", "image"]}# three_d
three-d = {path = "./static/three-d" , features=["egui-gui"] }
three-d-asset = {version = "0.9",features = ["hdr", "http"] }# sophus
sophus = { version = "0.11.0" }
sophus_autodiff = { version = "0.11.0" }
sophus_geo = "0.11.0"
sophus_image = "0.11.0"
sophus_lie = "0.11.0"
sophus_opt = "0.11.0"
sophus_renderer = "0.11.0"
sophus_sensor = "0.11.0"
sophus_sim = "0.11.0"
sophus_spline = "0.11.0"
sophus_tensor = "0.11.0"
sophus_timeseries = "0.11.0"
sophus_viewer = "0.11.0"
tokio = "1.43.0"
approx = "0.5.1"
bytemuck = "1.21.0"
thingbuf = "0.1.6"# rust-cv计算机视觉
cv = { version = "0.6.0" , features = ["default"] }
cv-core = "0.15.0"
cv-geom = "0.7.0"
cv-pinhole = "0.6.0"
akaze = "0.7.0"
eight-point = "0.8.0"
lambda-twist = "0.7.0"
image = "0.25.5"
imageproc = "0.25.0"# 最小二乘优化
gomez = "0.5.0"# 图优化
factrs = "0.2.0"# ORB角点检测
bye_orb_rs = { path = "./static/bye_orb_rs" }# 依赖覆盖
[patch.crates-io]
pulp = { path = "./static/pulp" }

原理简介

ORB角点检测概述

  • rust 角点检测库[https://github.com/ByeIO/bye.orb.rs]
  • Rublee E, Rabaud V, Konolige K, et al. ORB: An efficient alternative to SIFT or SURF[C]//2011 International conference on computer vision. Ieee, 2011: 2564-2571.
    关键词:BRIEF,FAST

ORB(Oriented FAST and Rotated BRIEF)是一种高效的角点检测和特征描述算法,由Ethan Rublee等人在2011年提出。ORB结合了FAST角点检测器和BRIEF描述子,并在其基础上进行了改进,使其在保持较高性能的同时,具有更好的旋转不变性和计算效率。

1. FAST角点检测

FAST(Features from Accelerated Segment Test)是一种高效的角点检测算法。其核心思想是通过比较像素点与其周围邻域像素的灰度值来判断是否为角点。FAST算法的步骤如下:

  • 选择一个像素点 ( p ),其灰度值为 ( I_p )。
  • 设定一个阈值 ( T )。
  • 检查以 ( p ) 为中心的圆形邻域(通常为16个像素)内的像素。
  • 如果邻域内有连续 ( N ) 个像素的灰度值都大于 ( I_p + T ) 或小于 ( I_p - T ),则 ( p ) 被认为是角点。

FAST算法的优点是计算速度快,适合实时应用。

2. BRIEF描述子

BRIEF(Binary Robust Independent Elementary Features)是一种二进制特征描述子,用于描述图像中的关键点。BRIEF通过比较关键点周围像素对的灰度值来生成一个二进制字符串,作为该关键点的描述子。BRIEF的优点是计算简单、存储空间小,适合大规模图像匹配。

3. ORB的改进

ORB在FAST和BRIEF的基础上进行了以下改进:

  • 方向性(Oriented):ORB通过计算关键点的方向(通常使用灰度质心法),使得BRIEF描述子具有旋转不变性。
  • 尺度不变性:ORB通过构建图像金字塔来实现尺度不变性。
  • 高效性:ORB在保持较高性能的同时,计算速度比SIFT和SURF更快,适合实时应用。

python的matplotlib设置中文字体

查找系统字体:

brew install font-noto-sans-cjk
fc-list :lang=zh | grep "得意黑"

设置:

from matplotlib import rcParams# 设置中文字体
rcParams['font.sans-serif'] = ['Smiley Sans']  # 指定默认字体为得意黑
# rcParams['font.sans-serif'] = ['Noto Sans CJK SC']  # 指定默认字体为Noto Sans
rcParams['axes.unicode_minus'] = False  # 解决保存图像时负号'-'显示为方块的问题

实现

原始cpp代码:[https://github.com/gaoxiang12/slambook2/blob/master/ch7/orb_cv.cpp]

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <chrono>using namespace std;
using namespace cv;int main(int argc, char **argv) {if (argc != 3) {cout << "usage: feature_extraction img1 img2" << endl;return 1;}//-- 读取图像Mat img_1 = imread(argv[1], CV_LOAD_IMAGE_COLOR);Mat img_2 = imread(argv[2], CV_LOAD_IMAGE_COLOR);assert(img_1.data != nullptr && img_2.data != nullptr);//-- 初始化std::vector<KeyPoint> keypoints_1, keypoints_2;Mat descriptors_1, descriptors_2;Ptr<FeatureDetector> detector = ORB::create();Ptr<DescriptorExtractor> descriptor = ORB::create();Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");//-- 第一步:检测 Oriented FAST 角点位置chrono::steady_clock::time_point t1 = chrono::steady_clock::now();detector->detect(img_1, keypoints_1);detector->detect(img_2, keypoints_2);//-- 第二步:根据角点位置计算 BRIEF 描述子descriptor->compute(img_1, keypoints_1, descriptors_1);descriptor->compute(img_2, keypoints_2, descriptors_2);chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);cout << "extract ORB cost = " << time_used.count() << " seconds. " << endl;Mat outimg1;drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);imshow("ORB features", outimg1);//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离vector<DMatch> matches;t1 = chrono::steady_clock::now();matcher->match(descriptors_1, descriptors_2, matches);t2 = chrono::steady_clock::now();time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);cout << "match ORB cost = " << time_used.count() << " seconds. " << endl;//-- 第四步:匹配点对筛选// 计算最小距离和最大距离auto min_max = minmax_element(matches.begin(), matches.end(),[](const DMatch &m1, const DMatch &m2) { return m1.distance < m2.distance; });double min_dist = min_max.first->distance;double max_dist = min_max.second->distance;printf("-- Max dist : %f \n", max_dist);printf("-- Min dist : %f \n", min_dist);//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.std::vector<DMatch> good_matches;for (int i = 0; i < descriptors_1.rows; i++) {if (matches[i].distance <= max(2 * min_dist, 30.0)) {good_matches.push_back(matches[i]);}}//-- 第五步:绘制匹配结果Mat img_match;Mat img_goodmatch;drawMatches(img_1, keypoints_1, img_2, keypoints_2, matches, img_match);drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch);imshow("all matches", img_match);imshow("good matches", img_goodmatch);waitKey(0);return 0;
}

重构的rust代码:

#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_imports)]
#![allow(unused_mut)]
#![allow(unused_assignments)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#![allow(rustdoc::missing_crate_level_docs)]
#![allow(unsafe_code)]
#![allow(clippy::undocumented_unsafe_blocks)]
#![allow(unused_must_use)]
#![allow(non_snake_case)]use image::{open, ImageBuffer, Rgb, DynamicImage
};
use imageproc::{drawing::draw_cross_mut, drawing::draw_line_segment_mut
};use bye_orb_rs::{orb, fast, common::Matchable
};use std::time::Instant;fn main() {let img1_path = "./assets/ch7-1.png";let img2_path = "./assets/ch7-2.png";main_orb(img1_path, img2_path);
}fn main_orb(img1_path: &str, img2_path: &str) {// 读取图像let mut img1 = open(img1_path).unwrap();let mut img2 = open(img2_path).unwrap();// 设置关键点数量let n_keypoints = 500;// 第一步: 检测Oriented FAST角点位置并计算BRIEF描述子let start_time = Instant::now();let img1_keypoints = orb::orb(&mut img1, n_keypoints).unwrap();let img2_keypoints = orb::orb(&mut img2, n_keypoints).unwrap();let end_time = Instant::now();println!("提取ORB特征点耗时: {:?} 秒", end_time - start_time);// 第二步: 使用Hamming距离进行匹配let start_time = Instant::now();let pair_indices = orb::match_brief(&img1_keypoints, &img2_keypoints);let end_time = Instant::now();println!("匹配ORB特征点耗时: {:?} 秒", end_time - start_time);// 第三步: 匹配点对筛选let mut matches: Vec<(usize, usize, f32)> = pair_indices.iter().map(|&(i, j)| (i, j, img1_keypoints[i].distance(&img2_keypoints[j]) as f32)).collect();matches.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap());let min_dist = matches[0].2;let max_dist = matches[matches.len() - 1].2;println!("-- 最大距离: {}", max_dist);println!("-- 最小距离: {}", min_dist);// 当描述子之间的距离大于两倍的最小距离时,认为匹配有误。但最小距离可能会非常小,设置一个经验值30作为下限。let good_matches: Vec<(usize, usize, f32)> = matches.iter().filter(|&&(_, _, dist)| dist <= (2.0 * min_dist).max(30.0)).cloned().collect();// 第四步: 绘制匹配结果let mut img1_rgb = img1.to_rgb8();let mut img2_rgb = img2.to_rgb8();// 绘制所有匹配点for &(i, j, _) in &matches {let kp1 = (img1_keypoints[i].x, img1_keypoints[i].y);let kp2 = (img2_keypoints[j].x, img2_keypoints[j].y);let color = Rgb([0, 255, 0]);draw_cross_mut(&mut img1_rgb, color, kp1.0 as i32, kp1.1 as i32);draw_cross_mut(&mut img2_rgb, color, kp2.0 as i32, kp2.1 as i32);}// 保存绘制结果img1_rgb.save("all_matches.png").unwrap();img2_rgb.save("all_matches2.png").unwrap();// 绘制筛选后的匹配点let mut img1_rgb_good = img1.to_rgb8();let mut img2_rgb_good = img2.to_rgb8();for &(i, j, _) in &good_matches {let kp1 = (img1_keypoints[i].x, img1_keypoints[i].y);let kp2 = (img2_keypoints[j].x, img2_keypoints[j].y);let color = Rgb([0, 255, 0]);draw_cross_mut(&mut img1_rgb_good, color, kp1.0 as i32, kp1.1 as i32);draw_cross_mut(&mut img2_rgb_good, color, kp2.0 as i32, kp2.1 as i32);}// 保存绘制结果img1_rgb_good.save("good_matches.png").unwrap();img2_rgb_good.save("good_matches2.png").unwrap();// 第五步: 创建并排放置的图像let (width1, height1) = img1_rgb.dimensions();let (width2, height2) = img2_rgb.dimensions();let total_width = width1 + width2;let max_height = height1.max(height2);let mut combined_image = ImageBuffer::new(total_width, max_height);// 将第一张图片复制到组合图像的左侧for y in 0..height1 {for x in 0..width1 {combined_image.put_pixel(x, y, *img1_rgb.get_pixel(x, y));}}// 将第二张图片复制到组合图像的右侧for y in 0..height2 {for x in 0..width2 {combined_image.put_pixel(width1 + x, y, *img2_rgb.get_pixel(x, y));}}// 绘制连接线for &(i, j, _) in &good_matches {let kp1 = (img1_keypoints[i].x, img1_keypoints[i].y);let kp2 = (img2_keypoints[j].x, img2_keypoints[j].y);let color = Rgb([0, 255, 0]);draw_line_segment_mut(&mut combined_image,(kp1.0 as f32, kp1.1 as f32),((width1 as f32 + kp2.0 as f32), kp2.1 as f32),color,);}// 保存并排放置的图像combined_image.save("combined_matches.png").unwrap();
}

python代码:

import cv2
import numpy as np
import matplotlib.pyplot as plt
import time
from matplotlib import rcParams# 设置中文字体
rcParams['font.sans-serif'] = ['Smiley Sans']  # 指定默认字体为得意黑
rcParams['axes.unicode_minus'] = False  # 解决保存图像时负号'-'显示为方块的问题def main(img1_path, img2_path):# 读取图像img1 = cv2.imread(img1_path, cv2.IMREAD_COLOR)img2 = cv2.imread(img2_path, cv2.IMREAD_COLOR)assert img1 is not None and img2 is not None, "图片读取失败"# 初始化ORB检测器orb = cv2.ORB_create()# 第一步: 检测Oriented FAST角点位置start_time = time.time()keypoints1 = orb.detect(img1, None)keypoints2 = orb.detect(img2, None)# 第二步: 计算BRIEF描述子keypoints1, descriptors1 = orb.compute(img1, keypoints1)keypoints2, descriptors2 = orb.compute(img2, keypoints2)end_time = time.time()print(f"提取ORB特征点耗时: {end_time - start_time} 秒")# 绘制特征点outimg1 = cv2.drawKeypoints(img1, keypoints1, None, color=(0, 255, 0), flags=0)plt.imshow(outimg1)plt.title("ORB特征点")plt.show()# 第三步: 使用Hamming距离进行匹配bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)start_time = time.time()matches = bf.match(descriptors1, descriptors2)end_time = time.time()print(f"匹配ORB特征点耗时: {end_time - start_time} 秒")# 第四步: 匹配点对筛选matches = sorted(matches, key=lambda x: x.distance)min_dist = matches[0].distancemax_dist = matches[-1].distanceprint(f"-- 最大距离: {max_dist}")print(f"-- 最小距离: {min_dist}")# 当描述子之间的距离大于两倍的最小距离时,认为匹配有误。但最小距离可能会非常小,设置一个经验值30作为下限。good_matches = [m for m in matches if m.distance <= max(2 * min_dist, 30.0)]# 第五步: 绘制匹配结果img_matches = cv2.drawMatches(img1, keypoints1, img2, keypoints2, matches, None, flags=2)img_good_matches = cv2.drawMatches(img1, keypoints1, img2, keypoints2, good_matches, None, flags=2)plt.imshow(img_matches)plt.title("所有匹配点")plt.show()plt.imshow(img_good_matches)plt.title("筛选后的匹配点")plt.show()if __name__ == "__main__":import sysmain("../assets/ch7-1.png", "../assets/ch7-2.png")

效果

rust输出:

提取ORB特征点耗时: 1.921584042s 秒
匹配ORB特征点耗时: 1.893269416s 秒
-- 最大距离: 49
-- 最小距离: 0

python输出:

提取ORB特征点耗时: 0.055989980697631836 秒
匹配ORB特征点耗时: 0.001383066177368164 秒
-- 最大距离: 81.0
-- 最小距离: 4.0
所有检测结果1 所有检测结果2 最佳检测结果1 最佳检测结果2 直线连接筛选后的匹配点 python结果1 python结果2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/873303.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DBSyncer开源数据同步中间件

一、简介 DBSyncer(英[dbsɪŋkɜː(r)],美[dbsɪŋkɜː(r) 简称dbs)是一款开源的数据同步中间件,提供MySQL、Oracle、SqlServer、PostgreSQL、Elasticsearch(ES)、Kafka、File、SQL等同步场景。支持上传插件自定义同步转换业务,提供监控全量和增量数据统计图、应用性能预…

幻想 实在 自我

小 C 和 小 Y 的故事还未停止 …… 或许不会停止弥晨时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下

AT+CSQ 和 AT+QENG查询的 RSSI不一致

1. 查询结果如下:2. CSQ查询的RSSI 接收信号强度指示时是31,对应但是 AT+QENG查询的是-128

记录printf的一个小问题

因为打算使用sprintf来格式化字符串,然后显示,遇到了一个符号类型的问题 printf("%d",now_adc);这一句,如果now_adc是uchar类型,输出字符会显示异常,后来了解到,如果要直接打印uchar只能用%x或者%s 所以如果不想改变量大小可以在前面加括号强转如printf("…

工业人工智能白皮书2025年:边缘AI驱动,助力新质生产力报告汇总PDF洞察(附原数据表)

原文链接:https://tecdat.cn/?p=38940 在当前科技变革的浪潮中,工业领域正经历着深刻转型。人工智能与工业互联网等前沿技术的迅猛发展,为工业的升级演进带来了前所未有的机遇与挑战。 本报告汇总洞察聚焦工业领域的变革趋势,深入剖析其核心驱动力与发展脉络。通过对工业 …

【视频】R语言支持向量分类器SVM原理及房价数据预测应用及回归、LASSO、决策树、随机森林、GBM、神经网络对比可视化

全文链接: https://tecdat.cn/?p=38830 原文出处:拓端数据部落公众号 分析师:Yuqi Liu 在大数据时代,精准的数据分类与预测对各领域的发展至关重要。超平面作为高维空间中的关键概念,可将线性空间一分为二,为数据分类奠定了理论基石。基于此发展而来的最大边缘分类器,…

【信息化】一个IT主管/经理/总监的该做什么?-读图解 CIO 工作指南上半IT管理总结

在这个IT人日益技术焦虑的年代,为了缓解一下学习的焦虑,看各路推荐开始学习一下《图解CIO工作指南》。我的初衷很简单,学习一下IT架构设计的思路,日常IT管理工作怎么优化,以及未来MBA毕业写IT规划论文。 一开始,当我拿到这本看似“老旧”的书籍时,心里确实犯了点嘀咕…

LCD-RGB屏幕学习(二)ESP32驱动RGB屏幕

ESP32是国内比较火的IOT芯片厂商,在个人玩家圈子里备受好评1.器件准备40pin RGB 通用接口屏幕这里的通用接口指的是市面上最常见的接口,并不属于某种标准 我拆开了吃灰已久的树莓派便携HDMI屏幕,在屏库上查看此型号,刚好满足需求,又剩下一笔大洋ESP32N16R8选用带有pasram的…

OM6621F低功耗低成本蓝牙芯片支持BLE5.1和2.4G私有协议集成了电源管理单元(PMU)BMS电池管理领域首选方案

OM6621Fx是一款专为蓝牙低功耗和专有2.4GHz应用设计的功率优化的真系统级芯片(SOC)解决方案。它集成了高性能和低功率的射频收发器,具有蓝牙基带和丰富的外围I0扩展。OM6621Fx还集成了电源管理单元(PMU),以实现高效率的电源管理。它针对2.4GHz蓝牙低功耗系统、专有2.4GHz系统…

Jetpack架构组件学习(6)——使用Glance实现桌面小组件

原文地址: Jetpack架构组件学习(6)——使用Glance实现桌面小组件-Stars-One的杂货小窝公司陆续整了几个Compose写的app,有个小组件的功能,顺便试了下Jetpack库里的Glance框架 感觉与原来的Remoteview差点意思,不过点击事件的使用比Remoteview要方便不少PS: 如果想看Remoteview实…

《操作系统真象还原》第九章 线程(二) 多线程轮转调度

本文是对《操作系统真象还原》第九章(二)学习的笔记,欢迎大家一起交流第九章 线程(二) 多线程轮转调度 本文是对《操作系统真象还原》第九章(二)学习的笔记,欢迎大家一起交流,目前所有代码已托管至 fdx-xdf/MyTinyOS 。 上一节中成功创建了线程并运行,这一节要实现的…

Cisco APIC 6.0(8e)M - 应用策略基础设施控制器

Cisco APIC 6.0(8e)M - 应用策略基础设施控制器Cisco APIC 6.0(8e)M - 应用策略基础设施控制器 Application Policy Infrastructure Controller (APIC) 请访问原文链接:https://sysin.org/blog/cisco-apic-6/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org思科…