Raspberry pi + camera

Has anyone successfully used rust on the raspberry pi + raspberry pi camera to take photos?

My goal is to just take a picture.

All the crates (raspicam, rascam)I've tried run into the same issue of relying on mmal but that has been removed from raspberry pi for a long time.

I'm currently seeing if I can use the libcamera bindings.

Would love pointers, I don't know much about writing my own bindings and have no idea what to do next.

Even simpler: if you have libcamera-still (cli) you should be able to call it with:

use std::process::Command;

fn main() {
    let status = Command::new("libcamera-still")
        .args(&["-o", "image.jpg", "--immediate"])
        .status()
        .expect("failed to run libcamera-still");

    if status.success() {
        println!("Image captured successfully!");
    } else {
        eprintln!("libcamera-still failed");
    }
}

That is the current solution I have!

But it's ugly and spawns a new process to do so.
It's good enough for now I just wanted to get my hands a little dirtier into the rust.

You will need libcamera. And current 0.4 is quite "raw". Following is the code I am using:

First, make sure of the camera id.

pub async fn camera_scan() -> Result<()> {
    let cam_mgr = CameraManager::new()?;
    let cams = cam_mgr.cameras();
    for i in 0..cams.len() {
        let cam = cams
            .get(i)
            .ok_or(eyre!("Cam {i} is gone when fetching from list"))?;
        println!("{}\t{}", i, cam.id());
        println!("{:?}", cam.properties());
        let cfgs = cam
            .generate_configuration(&[StreamRole::StillCapture])
            .ok_or(eyre!("Cannot get configuration from camera {}", cam.id()))?;
        for j in 0..cfgs.len() {
            println!("{i}.{j} available formats:");
            println!(
                "{:?}",
                cfgs.get(j)
                    .ok_or(eyre!(
                        "Cam {i} configration {j} is gone when fetching from list"
                    ))?
                    .formats()
            );
        }
    }
    Ok(())
}

Next taking the pics is quite tricky. I need an async env, hence following may contain "seems unnecessary" stuff:

    let shutter_button = Arc::new((Mutex::new(false), Condvar::new()));
    let (photo_tx, mut photo_rx) = mpsc::channel(4);
    let trigger = shutter_button.clone();
    let x = camera_id.to_owned();
    spawn_blocking(move || {
        if let Err(e) = camera_thread(&x, trigger, photo_tx) {
            tracing::error!("{e:?}");
        }
    })
    .await?;

        let (_, v) = &*shutter_button;
        v.notify_one();
        let photo_request_time = Utc::now();
        let image = photo_rx
            .recv()
            .await
            .ok_or(eyre!("Response channel closed"))?;

The image above is a DynamicImage from image crate. You can directly save it as some format or in my case, handed to OpenCV.

fn camera_thread(
    camera_id: &str,
    shutter_button: Arc<(Mutex<bool>, Condvar)>,
    photo_tx: Sender<DynamicImage>,
) -> Result<()> {
    let cam_mgr = CameraManager::new()?;
    let cams = cam_mgr.cameras();
    let mut chosen = None;
    for i in 0..cams.len() {
        let cam = cams
            .get(i)
            .ok_or(eyre!("Cam {i} is gone when fetching from list"))?;
        if cam.id() == camera_id {
            chosen = Some(cam);
            break;
        }
    }
    let cam = chosen.ok_or(eyre!("Could not find specified camera"))?;
    let mut cam = cam.acquire()?;

    let (tx, rx) = channel::<Request>();
    cam.on_request_completed(move |req| {
        if let Err(e) = tx.send(req) {
            tracing::warn!("Camera thread internal channel error: {e:?}");
        }
    });

    let mut cfgs = cam
        .generate_configuration(&[StreamRole::StillCapture])
        .ok_or(eyre!("Cannot get configuration from camera {}", cam.id()))?;
    let mut cfg = cfgs.get_mut(0).unwrap();
    cfg.set_pixel_format(PIXEL_FORMAT_RGB888);
    let max_size = cfg.formats().range(PIXEL_FORMAT_RGB888).max;
    cfg.set_size(max_size);
    match cfgs.validate() {
        CameraConfigurationStatus::Valid => (),
        CameraConfigurationStatus::Adjusted => {
            // What should I do about this?
            tracing::warn!("Configuration was adjusted.")
        }
        CameraConfigurationStatus::Invalid => Err(eyre!("Invalid camera configuration."))?,
    }
    cam.configure(&mut cfgs)?;

    let x: f64 = <f32 as Into<f64>>::into(EATING_CAT_THRESHOLD)
        * <u32 as Into<f64>>::into(max_size.height)
        * <u32 as Into<f64>>::into(max_size.width);
    MIN_CAT_SIZE
        .set(x.round() as u64)
        .map_err(|_| eyre!("Could not init MIN_CAT_SIZE"))?;

    let mut alloc = FrameBufferAllocator::new(&cam);
    let cfg = cfgs.get(0).unwrap();
    let stream = cfg.stream().unwrap();
    // The buffers len alloced is actually 1. Maybe related to `role`?
    let fb = alloc
        .alloc(&stream)?
        .pop()
        .ok_or(eyre!("Could not allocate even one framebuffer"))?;
    let fb = MemoryMappedFrameBuffer::new(fb)?;
    let mut req = cam
        .create_request(None)
        .ok_or(eyre!("Cannot create request"))?;
    req.add_buffer(&stream, fb)?;

    // The first photo status is FrameStartup, sleeping 1 sec did not help.
    // So take a photo and drop it.
    cam.start(None)?;
    cam.queue_request(req)?;
    req = rx.recv_timeout(Duration::from_secs(2))?;
    cam.stop()?;

    loop {
        // Wait until the trigger
        let (l, v) = &*shutter_button;
        let _not_care = l
            .lock()
            .and_then(|l| v.wait(l))
            .map_err(|e| eyre!("{e:?}"))?;

        req.reuse(ReuseFlag::REUSE_BUFFERS);
        cam.start(None)?;
        cam.queue_request(req)?;
        req = rx.recv_timeout(Duration::from_secs(2))?;
        cam.stop()?;

        let fb: &MemoryMappedFrameBuffer<FrameBuffer> =
            req.buffer(&stream).expect("Unmatched buffer types");
        let plane = fb.data().pop().ok_or(eyre!("No photo got"))?; // only 1 plane.
        let metadata = fb.metadata().ok_or(eyre!("No metadata got"))?;
        if metadata.status() == FrameMetadataStatus::Success {
            // let data_length = metadata
            //     .planes()
            //     .get(0)
            //     .ok_or(eyre!("No metadata planes"))?
            //     .bytes_used;
            // tracing::debug!("{} x {} = {data_length}", max_size.height, max_size.width);

            let image = ImageBuffer::from_vec(max_size.width, max_size.height, plane.to_vec())
                .ok_or(eyre!("Cannot create image, input not big enough"))?;
            let image = DynamicImage::ImageRgb8(image);

            photo_tx.blocking_send(image)?;
        }
    }
}