RustでRosbag2から画像を抽出するcrate
概要
- rosbag2のSQLite DBから直接画像を読み込むcrate を作った
- rosを立ち上げなくてもrosbagの画像を読み込むことができるので、オフライン処理に使える
- どちらかというとrosbag2の仕様と、RustでのSQLiteの扱い方についての記事
作ったもののlink
作ったもの
- Rosbag2から画像を読み込むところのinterfaceはこんなふうに使える
fn main() {
let args: Vec<String> = std::env::args().collect();
let file_name: String = args[1].to_string();
let mut interfaces: Vec<Rosbag2Images> = load_images_from_rosbag2(file_name).unwrap();
let mut frame_index = 0;
if !interfaces.is_empty() {
loop {
frame_index += 1;
let input_image = interfaces[0].get_frame();
if input_image.is_none() {
break;
}
my_image_proc(&input_image.unwrap(), frame_index);
}
}
}
作るにあたって
rosbag2について
The first plugin, sqlite3 is chosen by default. If not specified otherwise, rosbag2 will store and replay all recorded data in an SQLite3 database.
- なのでSQLite3のデータベースとして直接扱うことができる
rosbag2のschema
- messages
- TABLE
- id INTEGER PRIMARY KEY
- topic_id INTEGER NOT NULL
- timestamp INTEGER NOT NULL
- data BLOB NOT NULL
- INDEX: timestamp_idx ON messages (timestamp ASC);
- topics
- TABLE
- id INTEGER PRIMARY KEY
- name TEXT NOT NULL
- type TEXT NOT NULL
- serialization_format TEXT NOT NULL
- offered_qos_profiles TEXT NOT NULL
/// Topic の定義
#[derive(Debug)]
pub struct Topic {
pub id: u16,
pub name: String,
pub topic_type: String,
pub serialization_format: String,
pub offered_qos_profiles: String,
}
/// message の定義
pub struct TopicMessage {
pub message_id: u64,
pub topic_id: u16,
pub timestamp: u64,
pub data: Option<Vec<u8>>,
}
- 仕様として
- message はtopic_idを持っていて、topic_idからtopicのtypeとかわかる
- dataはu8にserializeされている
SQLiteをRustで扱う
message data の desrialize
- 結局この辺が一番時間かかった
- u8に変換されているのをdescializeする
/// Deserialize sensor_msgs/msg/Image to image vector.
///
/// ROS2 image message has data as below.
///
/// | the index of topic data | topic data |
/// | ------------------------------------ | ---------------------- |
/// | 0..(header.size - 1) | std_msgs/Header header |
/// | header.size..(header.size + 3) | uint32 height |
/// | (header.size + 4)..(header.size + 7) | uint32 width |
/// | | string encoding |
/// | | uint8 is_bigendian |
/// | | uint32 step |
/// | (header.datasize + 28) | uint8[] data |
pub fn deserialize_image_message(&self) -> RgbImage {
let topic_data: Vec<u8> = self.data.as_ref().unwrap().to_vec();
let header = get_header(&topic_data);
let height = get_u32(&topic_data, header.size);
let width = get_u32(&topic_data, header.size + 4);
RgbImage::from_vec(width, height, topic_data[(header.size + 28)..].to_vec()).unwrap()
}
pub fn get_u32(topic_data: &[u8], index: usize) -> u32 {
let u8_array = u32_slice_to_array(&topic_data[index..(index + 4)]);
unsafe { std::mem::transmute(u8_array) }
}
pub fn u32_slice_to_array(slice: &[u8]) -> [u8; 4] {
slice.try_into().expect("slice with incorrect length")
}
- string の deserialize
- 仕様をあんまり把握できてない、一旦自分の理解で
- 最初のu8 * 4つのdata bufferのうち、さらに最初のu8 * 1にstringの長さが入っている
- その後はutf8からStringに復元可能
/// Get string information.
///
/// Return.
///
/// - String: string data
/// - usize: end_index
pub fn get_string(topic_data: &[u8], start_index: usize) -> (String, usize) {
let end_index = start_index + 4 + topic_data[start_index] as usize + 1;
let string =
String::from_utf8(topic_data[(start_index + 4)..(end_index - 1)].to_vec()).unwrap();
(string, end_index)
}
最後に