Add simple camera-capture with nokhwa.
There might be better camera capture crates, this is just the first pick for now. Signed-off-by: Henner Zeller <h.zeller@acm.org>
This commit is contained in:
parent
b130450019
commit
a6077d755e
7 changed files with 696 additions and 46 deletions
4
hostcontrol/.gitignore
vendored
4
hostcontrol/.gitignore
vendored
|
|
@ -1 +1,5 @@
|
||||||
/target
|
/target
|
||||||
|
.envrc
|
||||||
|
.direnv/
|
||||||
|
*~
|
||||||
|
|
||||||
|
|
|
||||||
649
hostcontrol/Cargo.lock
generated
649
hostcontrol/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
clap = { version = "4.0", features = ["derive"] }
|
clap = { version = "4.0", features = ["derive"] }
|
||||||
nalgebra = { version = "0.33" }
|
nalgebra = { version = "0.33" }
|
||||||
serialport = { version = "4", default-features = false }
|
serialport = { version = "4", default-features = false }
|
||||||
|
|
@ -13,3 +14,5 @@ env_logger = "0.11.8"
|
||||||
egui = "0.31.1"
|
egui = "0.31.1"
|
||||||
eframe = "0.31.1"
|
eframe = "0.31.1"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
|
image = { version = "0.25", default-features = false, features = ["png","jpeg"] }
|
||||||
|
nokhwa = { version = "0.10.0", features = [ "input-native" ] }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
cargo
|
cargo
|
||||||
|
|
||||||
|
# Needed for a bindgen dependency in nokhwa
|
||||||
|
rustPlatform.bindgenHook
|
||||||
|
|
||||||
# Useful tools
|
# Useful tools
|
||||||
clippy
|
clippy
|
||||||
rustfmt
|
rustfmt
|
||||||
|
|
|
||||||
32
hostcontrol/src/camera.rs
Normal file
32
hostcontrol/src/camera.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use image::RgbImage;
|
||||||
|
use nokhwa::Camera as NokhwaCamera;
|
||||||
|
use nokhwa::pixel_format::RgbFormat;
|
||||||
|
use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType};
|
||||||
|
|
||||||
|
pub struct Camera {
|
||||||
|
camera: NokhwaCamera,
|
||||||
|
}
|
||||||
|
impl Camera {
|
||||||
|
pub fn new(camera_index: u32) -> Result<Self> {
|
||||||
|
let index = CameraIndex::Index(camera_index);
|
||||||
|
let format =
|
||||||
|
RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestResolution);
|
||||||
|
let mut camera = NokhwaCamera::new(index, format).context("could not find/access webcam")?;
|
||||||
|
camera.open_stream().context("failed to open stream")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
camera,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capture(&mut self) -> Result<RgbImage> {
|
||||||
|
for _ in 0..5 {
|
||||||
|
// Hack to have camera flush frames in buffer.
|
||||||
|
let _ = self.camera.frame();
|
||||||
|
}
|
||||||
|
let frame = self.camera.frame().context("Could not capture image")?;
|
||||||
|
println!("snap!");
|
||||||
|
Ok(frame.decode_image::<RgbFormat>()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ impl XYStage for GCodeStage {
|
||||||
a[1] / 1000.0,
|
a[1] / 1000.0,
|
||||||
a[2] / 1000.0
|
a[2] / 1000.0
|
||||||
))?;
|
))?;
|
||||||
|
self.stage_io.send_request("G4 P1\n")?; // make sure move finished.
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use std::{thread::sleep, time::Duration};
|
use std::path::PathBuf;
|
||||||
|
use image::RgbImage;
|
||||||
|
|
||||||
mod gcode_stage;
|
mod gcode_stage;
|
||||||
mod openflexure_stage;
|
mod openflexure_stage;
|
||||||
|
mod camera;
|
||||||
|
|
||||||
mod stage_io;
|
mod stage_io;
|
||||||
use crate::stage_io::StageIO;
|
use crate::stage_io::StageIO;
|
||||||
use crate::xy_stage::XYStage;
|
use crate::xy_stage::XYStage;
|
||||||
|
use crate::camera::Camera;
|
||||||
|
|
||||||
mod xy_stage;
|
mod xy_stage;
|
||||||
|
|
||||||
|
|
@ -21,8 +24,17 @@ struct CliArgs {
|
||||||
#[arg(long, default_value = "115200")]
|
#[arg(long, default_value = "115200")]
|
||||||
tty_speed: u32,
|
tty_speed: u32,
|
||||||
|
|
||||||
|
/// Stage movement backend
|
||||||
#[arg(long, value_enum)]
|
#[arg(long, value_enum)]
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
|
|
||||||
|
/// Id of camera to fetch images from.
|
||||||
|
#[arg(long, default_value = "0")]
|
||||||
|
camera_index: u32,
|
||||||
|
|
||||||
|
/// Directory all captured images are stored.
|
||||||
|
#[arg(long, value_name="out-dir")]
|
||||||
|
output_directory: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
// [derive(DebugCopy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
// [derive(DebugCopy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||||
|
|
@ -35,9 +47,29 @@ enum Backend {
|
||||||
GCode,
|
GCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn store_image(number: u32, pos: nalgebra::Vector3<f64>, dir: &PathBuf,
|
||||||
|
img: &RgbImage) {
|
||||||
|
let img_file =
|
||||||
|
&dir.join(format!("img-{:05}-{:0},{:0}.png", number, pos[0], pos[1]));
|
||||||
|
|
||||||
|
img.save(img_file).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = CliArgs::parse();
|
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
let args = CliArgs::parse();
|
||||||
|
|
||||||
|
if !args.output_directory.is_dir() {
|
||||||
|
panic!("--out-dir needs to be an existing directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
let origin = nalgebra::vector![0.0, 0.0, 0.0];
|
||||||
|
|
||||||
|
// Picture source
|
||||||
|
let mut cam = Camera::new(args.camera_index).unwrap();
|
||||||
|
cam.capture().unwrap(); // Warm up camera.
|
||||||
|
|
||||||
|
// Movement
|
||||||
let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap();
|
let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap();
|
||||||
let mut stage: Box<dyn XYStage> = match args.backend {
|
let mut stage: Box<dyn XYStage> = match args.backend {
|
||||||
Backend::GCode => Box::new(gcode_stage::GCodeStage::new(stage_io).unwrap()),
|
Backend::GCode => Box::new(gcode_stage::GCodeStage::new(stage_io).unwrap()),
|
||||||
|
|
@ -45,13 +77,17 @@ fn main() {
|
||||||
Box::new(openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap())
|
Box::new(openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let origin = nalgebra::vector![0.0, 0.0, 0.0];
|
|
||||||
stage.move_absolute_cartesian(origin).unwrap();
|
// Local testing. Just take a line of pictures.
|
||||||
sleep(Duration::from_secs(1));
|
|
||||||
let mut max_xy = stage.get_range();
|
let mut max_xy = stage.get_range();
|
||||||
|
// Only interested in lerp-ing X for now.
|
||||||
|
max_xy[1] = 0.0;
|
||||||
max_xy[2] = 0.0;
|
max_xy[2] = 0.0;
|
||||||
stage.move_absolute_cartesian(max_xy).unwrap();
|
|
||||||
sleep(Duration::from_secs(5));
|
for n in 0..10 {
|
||||||
|
stage.move_absolute_cartesian(max_xy * n as f64 / 10.0).unwrap();
|
||||||
|
store_image(n, origin, &args.output_directory, &cam.capture().unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue