圖:“Rust 的吉祥物 Ferris the Crab 化身監視器,手拿放大鏡監視都市的點點滴滴”,gemini-2.5-flash-preview,2025年09月16日。
身為 AI 工程師,要讓我們能夠使用 Rust 來進行 AI 應用的開發,讓程式碼能夠感受這個世界是至關重要的。感知世界的第一步 -- 電腦視覺,在 C/C++ 或 Python 生態系進行 AI 應用的開發一定知道 opencv
,甚至到了 “因為有 opencv
,所以我才放心用這三種語言” 的地步(或主要原因之一)。然而,要我們進入 Rust 的生態系中發展起來,讓我的程式碼能夠看見,這一定是不可逃避的靈魂拷問。
opencv
:我來解決你!
實際上,無論是圖片或影像串流幀其實只是一堆巨大的數字矩陣,opencv
提供了各種函數讓我們能夠輕易地操控這些數字矩陣,例如:讀取與寫入、色彩空間轉換、幾何變換、濾波與降噪、特徵提取、圖層疊加等等,這都在影像處理、電腦視覺領域至關重要,而在電腦視覺中已有相當成熟造詣的 AI 應用必定也如此。opencv
讓你的 Rust 宛如裝上一雙強大的眼睛,但眼睛連結到腦的過程是有點不一樣的。
OpenCV 是一個用 C++ 寫成的、擁有數十年歷史的龐大函式庫。而 Rust 中的 opencv
本身並不是用 Rust 從頭重新實作整個 OpenCV,而是靠 FFI (Foreign Function Interface) 的方式銜接 OpenCV 的底層 C++ 演算法。
在使用之前你一定要知道的運作模式:
所以,當我們在 Rust 中使用 opencv 時,我們其實是站在巨人的肩膀上,用 Rust 的安全韁繩,駕馭著 OpenCV 這匹性能猛獸。
[dependencies]
opencv = { version = "0.94.4", features = ["highgui", "videoio", "imgproc"] }
/// 此函數展示:
/// 1. 使用 imread 從檔案讀取圖像
/// 2. 轉換為灰階圖像
/// 3. 應用高斯模糊以減少雜訊
/// 4. 應用 Canny 邊緣檢測
/// 5. 使用 imwrite 保存結果
fn canny_edge_detection_tutorial() -> Result<(), Box<dyn std::error::Error>> {
// 設定輸入檔案路徑
let input_path = "data/test.jpg";
// 讀取彩色圖像
let img = imgcodecs::imread(input_path, imgcodecs::IMREAD_COLOR)?;
// 檢查圖像是否成功讀取
if img.empty() {
return Err("無法讀取圖像".into());
}
// 將彩色圖像轉換為灰階圖像
// 這是邊緣檢測的必要步驟,因為 Canny 演算法在灰階圖像上運作
let mut gray = Mat::default();
imgproc::cvt_color(&img, &mut gray, imgproc::COLOR_BGR2GRAY, 0, core::AlgorithmHint::ALGO_HINT_DEFAULT)?;
// 應用高斯模糊來減少圖像雜訊
// 這有助於減少假邊緣的產生,提高邊緣檢測的準確性
let mut blurred = Mat::default();
imgproc::gaussian_blur(&gray, &mut blurred, core::Size::new(5, 5), 0.0, 0.0, core::BORDER_DEFAULT, core::AlgorithmHint::ALGO_HINT_DEFAULT)?;
// 應用 Canny 邊緣檢測演算法
// 參數說明:
// - 50.0: 低閾值,用於檢測強邊緣
// - 150.0: 高閾值,用於定義連接弱邊緣
// - 3: Sobel 算子孔徑大小
// - false: 使用 L1 範數計算梯度幅度(較快)
let mut edges = Mat::default();
imgproc::canny(&blurred, &mut edges, 50.0, 150.0, 3, false)?;
// 保存邊緣檢測結果
let params = core::Vector::<i32>::new();
let output_path = "data/test_edges.jpg";
imgcodecs::imwrite(output_path, &edges, ¶ms)?;
Ok(())
}
/// 此函數展示:
/// 1. 將圖像轉換為灰階
/// 2. 應用二進制閾值處理
/// 3. 保存二值化結果
fn image_binarization_tutorial() -> Result<(), Box<dyn std::error::Error>> {
// 設定輸入檔案路徑
let input_path = "data/test.jpg";
// 讀取彩色圖像
let img = imgcodecs::imread(input_path, imgcodecs::IMREAD_COLOR)?;
// 檢查圖像是否成功讀取
if img.empty() {
return Err("無法讀取圖像進行二值化".into());
}
// 將彩色圖像轉換為灰階圖像
// 二值化處理需要在灰階圖像上進行
let mut gray = Mat::default();
imgproc::cvt_color(&img, &mut gray, imgproc::COLOR_BGR2GRAY, 0, core::AlgorithmHint::ALGO_HINT_DEFAULT)?;
// 設定閾值參數
let mut binary = Mat::default();
let threshold_value = 127.0; // 標準閾值 (0-255 的中間值)
let max_value = 255.0; // 二值化圖像的最大值 (白色)
// 應用二進制閾值處理
// 像素值 > 閾值 設為 max_value (白色)
// 像素值 <= 閾值 設為 0 (黑色)
imgproc::threshold(&gray, &mut binary, threshold_value, max_value, imgproc::THRESH_BINARY)?;
let binary_output = "data/test_binary.jpg";
let params = core::Vector::<i32>::new();
imgcodecs::imwrite(binary_output, &binary, ¶ms)?;
Ok(())
}
/// 此函數展示:
/// 1. 繪製一個邊界框
/// 2. 添加文字標籤
/// 3. 保存註解後的圖像
fn bounding_box_text_tutorial() -> Result<(), Box<dyn std::error::Error>> {
let input_path = "data/test.jpg";
// 讀取圖像
let img = imgcodecs::imread(input_path, imgcodecs::IMREAD_COLOR)?;
if img.empty() {
return Err("無法讀取圖像進行註解".into());
}
// 創建圖像副本進行註解
let mut annotated_img = img.clone();
// 繪製一個紅色邊界框
let bbox = core::Rect::new(100, 100, 200, 150);
imgproc::rectangle(&mut annotated_img, bbox, Scalar::new(0.0, 0.0, 255.0, 0.0), 3, imgproc::LINE_8, 0)?;
// 添加文字標籤
let text = "Object";
imgproc::put_text(&mut annotated_img, text, core::Point::new(bbox.x, bbox.y - 10),
imgproc::FONT_HERSHEY_SIMPLEX, 0.8, Scalar::new(0.0, 0.0, 255.0, 0.0), 2, imgproc::LINE_8, false)?;
// 保存註解後的圖像
let annotated_output = "data/test_annotated.jpg";
let params = core::Vector::<i32>::new();
imgcodecs::imwrite(annotated_output, &annotated_img, ¶ms)?;
Ok(())
}
/// 此函數展示:
/// 1. 開啟並讀取影片檔案
/// 2. 逐幀處理
/// 3. 在影片幀上添加註解
/// 4. 即時顯示與 imshow
/// 5. 鍵盤輸入處理
/// 6. 使用 VideoWriter 保存註解影片
/// 7. 資源清理
fn video_annotation_tutorial() -> Result<(), Box<dyn std::error::Error>> {
// 設定輸入影片檔案路徑
let video_path = "data/test_short.mp4";
// 檢查影片檔案是否存在
if !Path::new(video_path).exists() {
return Ok(());
}
// 開啟影片檔案
let mut cap = videoio::VideoCapture::from_file(video_path, videoio::CAP_ANY)?;
// 檢查影片是否成功開啟
if !cap.is_opened()? {
return Err("無法開啟影片檔案".into());
}
// 獲取影片屬性
let fps = cap.get(videoio::CAP_PROP_FPS)?;
let width = cap.get(videoio::CAP_PROP_FRAME_WIDTH)? as i32;
let height = cap.get(videoio::CAP_PROP_FRAME_HEIGHT)? as i32;
// 創建顯示視窗
let window_name = "Video Annotation Tutorial";
highgui::named_window(window_name, highgui::WINDOW_AUTOSIZE)?;
// 初始化 VideoWriter 用於輸出影片
let output_video_path = "data/annotated_video.mp4";
// MP4 編碼格式
let fourcc = videoio::VideoWriter::fourcc('m', 'p', '4', 'v')?;
// 創建 VideoWriter,使用與輸入影片相同的屬性
let mut writer = videoio::VideoWriter::new(output_video_path, fourcc, fps, core::Size::new(width, height), true)?;
// 檢查 VideoWriter 是否成功創建
if !writer.is_opened()? {
return Err("無法創建輸出影片檔案".into());
}
// 初始化處理變數
let mut frame = Mat::default();
// 逐幀處理影片
while cap.read(&mut frame)? {
// 檢查是否到達影片結尾
if frame.empty() {
break;
}
// 為幀添加註解
let mut annotated_frame = frame.clone();
// 計算邊界框的中心位置
let box_width = 200;
let box_height = 100;
let center_x = width / 2;
let center_y = height / 2;
let box_x = center_x - box_width / 2;
let box_y = center_y - box_height / 2;
// 在中心繪製邊界框
let bbox = core::Rect::new(box_x, box_y, box_width, box_height);
imgproc::rectangle(&mut annotated_frame, bbox, Scalar::new(0.0, 255.0, 0.0, 0.0), 3, imgproc::LINE_8, 0)?;
// 在邊界框上方添加文字
let text = "Center Detection";
let text_x = box_x;
let text_y = box_y - 10;
imgproc::put_text(&mut annotated_frame, text, core::Point::new(text_x, text_y),
imgproc::FONT_HERSHEY_DUPLEX, 0.8, Scalar::new(0.0, 255.0, 0.0, 0.0), 2, imgproc::LINE_8, false)?;
// 將註解後的幀寫入輸出影片
writer.write(&annotated_frame)?;
// 顯示註解後的幀
highgui::imshow(window_name, &annotated_frame)?;
// 處理鍵盤輸入
let key = highgui::wait_key(30)?; // 等待 30ms 用於按鍵
match key {
113 | 27 => {
break;
},
_ => {}
}
}
// 資源清理
cap.release()?;
writer.release()?;
highgui::destroy_window(window_name)?;
Ok(())
}
這四個簡單的範例初探了 opencv
已經相當成熟的影像處理流程,讓從 C/C++ 和 Python 生態系過來後,並不會感到陌生,關鍵名詞與邏輯架構也都幾乎沿用。當然的不只有 OpenCV,純 Rust 實作影像處理也有很多開源專案正在成長當中,雖然這已經是相當成熟的領域,但我相信未來仍有驚喜。
持續增加我們手中的工具吧!
https://github.com/liren0907/rust_one