├── .dockerignore ├── .gitattributes ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE.md ├── README.md ├── haarcascade_frontalface_alt.xml └── src ├── capture.rs ├── main.rs └── window.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .git/ 3 | .idea/ 4 | *.md 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.xml filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/rust 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust 3 | 4 | ### Rust ### 5 | # Generated by Cargo 6 | # will have compiled files and executables 7 | /target/ 8 | 9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 11 | !Cargo.lock 12 | 13 | # These are backup files generated by rustfmt 14 | **/*.rs.bk 15 | 16 | .idea/ 17 | 18 | # End of https://www.toptal.com/developers/gitignore/api/rust,clion 19 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.13" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "1.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 19 | 20 | [[package]] 21 | name = "cc" 22 | version = "1.0.59" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" 25 | dependencies = [ 26 | "jobserver", 27 | ] 28 | 29 | [[package]] 30 | name = "clang" 31 | version = "2.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "84c044c781163c001b913cd018fc95a628c50d0d2dfea8bca77dad71edb16e37" 34 | dependencies = [ 35 | "clang-sys", 36 | "libc", 37 | ] 38 | 39 | [[package]] 40 | name = "clang-sys" 41 | version = "1.0.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "9da1484c6a890e374ca5086062d4847e0a2c1e5eba9afa5d48c09e8eb39b2519" 44 | dependencies = [ 45 | "glob", 46 | "libc", 47 | ] 48 | 49 | [[package]] 50 | name = "dunce" 51 | version = "1.0.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "b2641c4a7c0c4101df53ea572bffdc561c146f6c2eb09e4df02bc4811e3feeb4" 54 | 55 | [[package]] 56 | name = "facedetect" 57 | version = "0.1.0" 58 | dependencies = [ 59 | "opencv", 60 | ] 61 | 62 | [[package]] 63 | name = "glob" 64 | version = "0.3.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 67 | 68 | [[package]] 69 | name = "jobserver" 70 | version = "0.1.26" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" 73 | dependencies = [ 74 | "libc", 75 | ] 76 | 77 | [[package]] 78 | name = "lazy_static" 79 | version = "1.4.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 82 | 83 | [[package]] 84 | name = "libc" 85 | version = "0.2.76" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" 88 | 89 | [[package]] 90 | name = "memchr" 91 | version = "2.3.3" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 94 | 95 | [[package]] 96 | name = "num-traits" 97 | version = "0.2.12" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 100 | dependencies = [ 101 | "autocfg", 102 | ] 103 | 104 | [[package]] 105 | name = "once_cell" 106 | version = "1.4.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" 109 | 110 | [[package]] 111 | name = "opencv" 112 | version = "0.88.5" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "980aa24534b9bcfb03c259779ffcbe422e0395cf45700d6d85657734ea1d5c57" 115 | dependencies = [ 116 | "cc", 117 | "dunce", 118 | "jobserver", 119 | "libc", 120 | "num-traits", 121 | "once_cell", 122 | "opencv-binding-generator", 123 | "pkg-config", 124 | "semver", 125 | "shlex", 126 | "vcpkg", 127 | ] 128 | 129 | [[package]] 130 | name = "opencv-binding-generator" 131 | version = "0.82.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "e4ac010a66cd1e1dc457c20d467a16286cc83381307cace05357b414c06740f6" 134 | dependencies = [ 135 | "clang", 136 | "clang-sys", 137 | "dunce", 138 | "once_cell", 139 | "percent-encoding", 140 | "regex", 141 | ] 142 | 143 | [[package]] 144 | name = "percent-encoding" 145 | version = "2.1.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 148 | 149 | [[package]] 150 | name = "pkg-config" 151 | version = "0.3.18" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" 154 | 155 | [[package]] 156 | name = "regex" 157 | version = "1.3.9" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 160 | dependencies = [ 161 | "aho-corasick", 162 | "memchr", 163 | "regex-syntax", 164 | "thread_local", 165 | ] 166 | 167 | [[package]] 168 | name = "regex-syntax" 169 | version = "0.6.18" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 172 | 173 | [[package]] 174 | name = "semver" 175 | version = "1.0.20" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 178 | 179 | [[package]] 180 | name = "shlex" 181 | version = "1.2.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" 184 | 185 | [[package]] 186 | name = "thread_local" 187 | version = "1.0.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 190 | dependencies = [ 191 | "lazy_static", 192 | ] 193 | 194 | [[package]] 195 | name = "vcpkg" 196 | version = "0.2.10" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 199 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "facedetect" 3 | version = "0.1.0" 4 | authors = ["Markus Mayer "] 5 | 6 | [dependencies] 7 | opencv = "0.88" 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile provides an example of a build environment for Ubuntu 20.04, OpenCV 4.2 and opencv-rust 0.45. 2 | # Note that the resulting image is neither optimized for size nor meant to run the produced binary. 3 | 4 | FROM ubuntu:20.04 AS builder 5 | 6 | ENV DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install base environment. 9 | RUN apt-get update 10 | RUN apt-get install -y \ 11 | curl \ 12 | build-essential \ 13 | git 14 | 15 | # Install Rust 1.46. 16 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh && sh /tmp/rustup.sh -y 17 | 18 | # Install dependencies for opencv-rust. 19 | RUN apt-get install -y \ 20 | clang \ 21 | libopencv-dev \ 22 | libclang-dev 23 | 24 | # Building the application. 25 | WORKDIR /usr/src/rust-facedetect 26 | RUN $HOME/.cargo/bin/rustup override set 1.46.0 27 | 28 | COPY Cargo.* ./ 29 | COPY src/ ./src 30 | 31 | RUN $HOME/.cargo/bin/cargo build --release 32 | 33 | # Application built as ./target/release/facedetect 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright 2020 — Markus Mayer 4 | 5 | Original example code: Copyright 2016-2018 — The OpenCV-Rust binding developers 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCV Face Detection in Rust 2 | 3 | An experiment with [opencv-rust](https://github.com/twistedfall/opencv-rust) and basically not much more than the multi-scale face detection demo 4 | (using a frontal-face [Haar cascade](https://docs.opencv.org/4.2.0/db/d28/tutorial_cascade_classifier.html)) on the first video capture device that can be found. 5 | 6 | To run it, execute 7 | 8 | ```bash 9 | cargo run 10 | ``` 11 | 12 | To exit, press `ESC`. 13 | 14 | ## Startup errors 15 | 16 | If you get errors such as 17 | 18 | ``` 19 | [ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (1100) open OpenCV | GStreamer warning: Cannot query video position: status=0, value=-1, duration=-1 20 | [ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (2075) handleMessage OpenCV | GStreamer warning: Embedded video playback halted; module v4l2src0 reported: Internal data stream error. 21 | [ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (651) startPipeline OpenCV | GStreamer warning: unable to start pipeline 22 | [ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (1257) setProperty OpenCV | GStreamer warning: no pipeline 23 | thread 'main' panicked at src/main.rs:41:9: 24 | Unable to open default camera! 25 | note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 26 | [ WARN:0] global ./modules/videoio/src/cap_gstreamer.cpp (616) isPipelinePlaying OpenCV | GStreamer warning: GStreamer: pipeline have not been created 27 | ``` 28 | 29 | then the default camera parameters are incorrect for your device. 30 | To select different parameters, use the `CAPTURE_WIDTH`, `CAPTURE_HEIGHT` and `CAPTURE_DEVICE` environment variables: 31 | 32 | ```shell 33 | CAPTURE_WIDTH=848 CAPTURE_HEIGHT=480 CAPTURE_DEVICE=1 cargo run --release 34 | ``` 35 | -------------------------------------------------------------------------------- /haarcascade_frontalface_alt.xml: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:891f1af7c9ecd07ece294987a966c6fbcc42bf25e98e3840f60d8570c6a29678 3 | size 919871 4 | -------------------------------------------------------------------------------- /src/capture.rs: -------------------------------------------------------------------------------- 1 | use opencv::{prelude::*, videoio}; 2 | 3 | type Result = opencv::Result; 4 | 5 | pub(crate) struct Capture { 6 | capture: videoio::VideoCapture, 7 | } 8 | 9 | impl Capture { 10 | pub fn create_default(width: i32, height: i32) -> Result { 11 | Self::create(0, width, height) 12 | } 13 | 14 | pub fn create(index: i32, width: i32, height: i32) -> Result { 15 | let mut capture = videoio::VideoCapture::new(index, videoio::CAP_ANY)?; 16 | capture.set(videoio::CAP_PROP_FRAME_WIDTH, width as f64)?; 17 | capture.set(videoio::CAP_PROP_FRAME_HEIGHT, height as f64)?; 18 | Ok(Self { capture }) 19 | } 20 | 21 | pub fn is_opened(&self) -> Result { 22 | videoio::VideoCapture::is_opened(&self.capture) 23 | } 24 | 25 | pub fn grab_frame(&mut self) -> Result> { 26 | if !self.capture.grab()? { 27 | return Ok(None); 28 | } 29 | 30 | let mut frame = Mat::default(); 31 | self.capture.retrieve(&mut frame, 0)?; 32 | Ok(Some(frame)) 33 | } 34 | } 35 | 36 | impl Drop for Capture { 37 | fn drop(&mut self) { 38 | let _ = self.capture.release(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod capture; 2 | mod window; 3 | 4 | extern crate opencv; 5 | use crate::capture::Capture; 6 | use crate::window::Window; 7 | use opencv::core::{Rect, Scalar, Size}; 8 | use opencv::{highgui, imgproc, objdetect, prelude::*, types}; 9 | 10 | type Result = opencv::Result; 11 | 12 | const WINDOW_NAME: &str = "OpenCV Face Detection in Rust"; 13 | const CASCADE_XML_FILE: &str = "haarcascade_frontalface_alt.xml"; 14 | 15 | const DEFAULT_CAPTURE_WIDTH: i32 = 800; 16 | const DEFAULT_CAPTURE_HEIGHT: i32 = 600; 17 | 18 | const SCALE_FACTOR: f64 = 0.25f64; 19 | const SCALE_FACTOR_INV: i32 = (1f64 / SCALE_FACTOR) as i32; 20 | 21 | fn run() -> Result<()> { 22 | let mut classifier = objdetect::CascadeClassifier::new(CASCADE_XML_FILE)?; 23 | 24 | // Select capture width. 25 | let capture_width = match get_capture_width() { 26 | Ok(value) => value, 27 | Err(error) => return Err(error), 28 | }; 29 | 30 | // Select capture height. 31 | let capture_height = match get_capture_height() { 32 | Ok(value) => value, 33 | Err(error) => return Err(error), 34 | }; 35 | 36 | // Select capture device. 37 | let mut capture = get_capture_device(capture_width, capture_height)?; 38 | 39 | let opened = capture.is_opened()?; 40 | if !opened { 41 | panic!("Unable to open default camera!"); 42 | } 43 | 44 | let window = Window::create(WINDOW_NAME, capture_width, capture_height)?; 45 | 46 | run_main_loop(&mut capture, &mut classifier, &window)?; 47 | 48 | Ok(()) 49 | } 50 | 51 | fn get_capture_device(capture_width: i32, capture_height: i32) -> Result { 52 | if let Ok(value) = std::env::var("CAPTURE_DEVICE") { 53 | if let Ok(index) = value.parse() { 54 | println!("Using capture device #{index}"); 55 | Capture::create(index, capture_width, capture_height) 56 | } else { 57 | let error = format!("Invalid camera selection: CAPTURE_DEVICE={value}"); 58 | Err(opencv::Error::new(1, error)) 59 | } 60 | } else { 61 | println!("Using default capture device"); 62 | Capture::create_default(capture_width, capture_height) 63 | } 64 | } 65 | 66 | fn get_capture_height() -> Result { 67 | if let Ok(value) = std::env::var("CAPTURE_HEIGHT") { 68 | if let Ok(height) = value.parse() { 69 | println!("Using capture height {height}"); 70 | Ok(height) 71 | } else { 72 | let error = format!("Invalid capture height: CAPTURE_HEIGHT={value}"); 73 | Err(opencv::Error::new(1, error)) 74 | } 75 | } else { 76 | println!("Using default capture height ({DEFAULT_CAPTURE_HEIGHT})"); 77 | Ok(DEFAULT_CAPTURE_HEIGHT) 78 | } 79 | } 80 | 81 | fn get_capture_width() -> Result { 82 | if let Ok(value) = std::env::var("CAPTURE_WIDTH") { 83 | if let Ok(width) = value.parse() { 84 | println!("Using capture width {width}"); 85 | Ok(width) 86 | } else { 87 | let error = format!("Invalid capture width: CAPTURE_WIDTH={value}"); 88 | Err(opencv::Error::new(1, error)) 89 | } 90 | } else { 91 | println!("Using default capture width ({DEFAULT_CAPTURE_WIDTH})"); 92 | Ok(DEFAULT_CAPTURE_WIDTH) 93 | } 94 | } 95 | 96 | fn run_main_loop( 97 | capture: &mut Capture, 98 | classifier: &mut objdetect::CascadeClassifier, 99 | window: &Window, 100 | ) -> Result<()> { 101 | loop { 102 | const KEY_CODE_ESCAPE: i32 = 27; 103 | if let Ok(KEY_CODE_ESCAPE) = highgui::wait_key(10) { 104 | return Ok(()); 105 | } 106 | 107 | let mut frame = match capture.grab_frame()? { 108 | Some(frame) => frame, 109 | None => continue, 110 | }; 111 | 112 | let preprocessed = preprocess_image(&frame)?; 113 | let faces = detect_faces(classifier, preprocessed)?; 114 | for face in faces { 115 | draw_box_around_face(&mut frame, face)?; 116 | } 117 | 118 | window.show_image(&frame)?; 119 | } 120 | } 121 | 122 | fn preprocess_image(frame: &Mat) -> Result { 123 | let gray = convert_to_grayscale(frame)?; 124 | let reduced = reduce_image_size(&gray, SCALE_FACTOR)?; 125 | equalize_image(&reduced) 126 | } 127 | 128 | fn convert_to_grayscale(frame: &Mat) -> Result { 129 | let mut gray = Mat::default(); 130 | imgproc::cvt_color(frame, &mut gray, imgproc::COLOR_BGR2GRAY, 0)?; 131 | Ok(gray) 132 | } 133 | 134 | fn reduce_image_size(gray: &Mat, factor: f64) -> Result { 135 | // Destination size is determined by scaling `factor`, not by target size. 136 | const SIZE_AUTO: Size = Size { 137 | width: 0, 138 | height: 0, 139 | }; 140 | let mut reduced = Mat::default(); 141 | imgproc::resize( 142 | gray, 143 | &mut reduced, 144 | SIZE_AUTO, 145 | factor, // fx 146 | factor, // fy 147 | imgproc::INTER_LINEAR, 148 | )?; 149 | Ok(reduced) 150 | } 151 | 152 | fn equalize_image(reduced: &Mat) -> Result { 153 | let mut equalized = Mat::default(); 154 | imgproc::equalize_hist(reduced, &mut equalized)?; 155 | Ok(equalized) 156 | } 157 | 158 | fn detect_faces( 159 | classifier: &mut objdetect::CascadeClassifier, 160 | image: Mat, 161 | ) -> Result { 162 | const SCALE_FACTOR: f64 = 1.1; 163 | const MIN_NEIGHBORS: i32 = 2; 164 | const FLAGS: i32 = 0; 165 | const MIN_FACE_SIZE: Size = Size { 166 | width: 30, 167 | height: 30, 168 | }; 169 | const MAX_FACE_SIZE: Size = Size { 170 | width: 0, 171 | height: 0, 172 | }; 173 | 174 | let mut faces = types::VectorOfRect::new(); 175 | classifier.detect_multi_scale( 176 | &image, 177 | &mut faces, 178 | SCALE_FACTOR, 179 | MIN_NEIGHBORS, 180 | FLAGS, 181 | MIN_FACE_SIZE, 182 | MAX_FACE_SIZE, 183 | )?; 184 | Ok(faces) 185 | } 186 | 187 | fn draw_box_around_face(frame: &mut Mat, face: Rect) -> Result<()> { 188 | println!("found face {:?}", face); 189 | let scaled_face = Rect { 190 | x: face.x * SCALE_FACTOR_INV, 191 | y: face.y * SCALE_FACTOR_INV, 192 | width: face.width * SCALE_FACTOR_INV, 193 | height: face.height * SCALE_FACTOR_INV, 194 | }; 195 | 196 | const THICKNESS: i32 = 2; 197 | const LINE_TYPE: i32 = 8; 198 | const SHIFT: i32 = 0; 199 | let color_red = Scalar::new(0f64, 0f64, 255f64, -1f64); 200 | 201 | imgproc::rectangle(frame, scaled_face, color_red, THICKNESS, LINE_TYPE, SHIFT)?; 202 | Ok(()) 203 | } 204 | 205 | fn main() { 206 | run().unwrap() 207 | } 208 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use opencv::{highgui, prelude::*}; 2 | 3 | type Result = opencv::Result; 4 | 5 | pub(crate) struct Window { 6 | name: String, 7 | } 8 | 9 | impl Window { 10 | pub fn create(name: &'_ str, width: i32, height: i32) -> Result { 11 | highgui::named_window(name, highgui::WINDOW_GUI_NORMAL | highgui::WINDOW_KEEPRATIO)?; 12 | highgui::resize_window(name, width, height)?; 13 | Ok(Self { 14 | name: name.to_owned(), 15 | }) 16 | } 17 | 18 | pub fn show_image(&self, frame: &Mat) -> Result<()> { 19 | highgui::imshow(&self.name, &frame) 20 | } 21 | } 22 | 23 | impl Drop for Window { 24 | fn drop(&mut self) { 25 | let _ = highgui::destroy_window(&self.name); 26 | } 27 | } 28 | --------------------------------------------------------------------------------