use crate::{
CameraControl, CameraFormat, CameraIndexType, CameraInfo, CaptureAPIBackend,
CaptureBackendTrait, FrameFormat, KnownCameraControls, NokhwaError, Resolution,
};
use image::{ImageBuffer, Rgb};
use opencv::{
core::{Mat, MatTraitConst, MatTraitConstManual, Vec3b},
videoio::{
VideoCapture, VideoCaptureTrait, VideoCaptureTraitConst, CAP_ANY, CAP_AVFOUNDATION,
CAP_MSMF, CAP_PROP_FPS, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, CAP_V4L2,
},
};
use std::{any::Any, borrow::Cow, collections::HashMap};
macro_rules! tryinto_num {
($to:ty, $from:expr) => {{
use std::convert::TryFrom;
match <$to>::try_from($from) {
Ok(v) => v,
Err(why) => {
return Err(crate::NokhwaError::GeneralError(format!(
"Failed to convert {}, {}",
$from,
why.to_string()
)))
}
}
}};
}
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "input-opencv")))]
pub struct OpenCvCaptureDevice {
camera_format: CameraFormat,
camera_location: CameraIndexType,
camera_info: CameraInfo,
api_preference: i32,
video_capture: VideoCapture,
}
#[allow(clippy::must_use_candidate)]
impl OpenCvCaptureDevice {
pub fn new(
camera_location: CameraIndexType,
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
let api = if let Some(a) = api_pref {
tryinto_num!(i32, a)
} else {
tryinto_num!(i32, get_api_pref_int())
};
let mut index = i32::MAX as u32;
let camera_format = match cfmt {
Some(cam_fmt) => cam_fmt,
None => CameraFormat::default(),
};
let mut video_capture = match camera_location.clone() {
CameraIndexType::Index(idx) => {
let vid_cap = match VideoCapture::new(tryinto_num!(i32, idx), api) {
Ok(vc) => {
index = idx;
vc
}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
idx.to_string(),
why.to_string(),
))
}
};
vid_cap
}
CameraIndexType::IPCamera(ip) => match VideoCapture::from_file(&*ip, CAP_ANY) {
Ok(vc) => vc,
Err(why) => return Err(NokhwaError::OpenDeviceError(ip, why.to_string())),
},
};
set_properties(&mut video_capture, camera_format, &camera_location)?;
let camera_info = CameraInfo::new(
format!("OpenCV Capture Device {}", camera_location),
camera_location.to_string(),
"".to_string(),
index as usize,
);
Ok(OpenCvCaptureDevice {
camera_format,
camera_location,
camera_info,
api_preference: api,
video_capture,
})
}
pub fn new_ip_camera(ip: String) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::IPCamera(ip);
OpenCvCaptureDevice::new(camera_location, None, None)
}
pub fn new_index_camera(
index: usize,
cfmt: Option<CameraFormat>,
api_pref: Option<u32>,
) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(camera_location, cfmt, api_pref)
}
pub fn new_autopref(index: usize, cfmt: Option<CameraFormat>) -> Result<Self, NokhwaError> {
let camera_location = CameraIndexType::Index(tryinto_num!(u32, index));
OpenCvCaptureDevice::new(camera_location, cfmt, None)
}
pub fn is_ip_camera(&self) -> bool {
match self.camera_location {
CameraIndexType::Index(_) => false,
CameraIndexType::IPCamera(_) => true,
}
}
pub fn is_index_camera(&self) -> bool {
match self.camera_location {
CameraIndexType::Index(_) => true,
CameraIndexType::IPCamera(_) => false,
}
}
pub fn camera_location(&self) -> CameraIndexType {
self.camera_location.clone()
}
pub fn opencv_preference(&self) -> i32 {
self.api_preference
}
#[allow(clippy::cast_sign_loss)]
pub fn raw_frame_vec(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
if !self.is_stream_open() {
return Err(NokhwaError::ReadFrameError(
"Stream is not open!".to_string(),
));
}
let mut frame = Mat::default();
match self.video_capture.read(&mut frame) {
Ok(a) => {
if !a {
return Err(NokhwaError::ReadFrameError(
"Failed to read frame from videocapture: OpenCV return false, camera disconnected?".to_string(),
));
}
}
Err(why) => {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to read frame from videocapture: {}",
why
)))
}
}
if frame.empty() {
return Err(NokhwaError::ReadFrameError("Frame Empty!".to_string()));
}
match frame.size() {
Ok(size) => {
if size.width > 0 {
return if frame.is_continuous() {
let mut raw_vec: Vec<u8> = Vec::new();
let frame_data_vec = match Mat::data_typed::<Vec3b>(&frame) {
Ok(v) => v,
Err(why) => {
return Err(NokhwaError::ReadFrameError(format!(
"Failed to convert frame into raw Vec3b: {}",
why
)))
}
};
for pixel in frame_data_vec.iter() {
let pixel_slice: &[u8; 3] = &**pixel;
raw_vec.push(pixel_slice[2]);
raw_vec.push(pixel_slice[1]);
raw_vec.push(pixel_slice[0]);
}
Ok(Cow::from(raw_vec))
} else {
Err(NokhwaError::ReadFrameError(
"Failed to read frame from videocapture: not cont".to_string(),
))
};
}
Err(NokhwaError::ReadFrameError(
"Frame width is less than zero!".to_string(),
))
}
Err(why) => Err(NokhwaError::ReadFrameError(format!(
"Failed to read frame from videocapture: failed to read size: {}",
why
))),
}
}
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
pub fn raw_resolution(&self) -> Result<Resolution, NokhwaError> {
let width = match self.video_capture.get(CAP_PROP_FRAME_WIDTH) {
Ok(width) => width as u32,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Width".to_string(),
error: why.to_string(),
})
}
};
let height = match self.video_capture.get(CAP_PROP_FRAME_HEIGHT) {
Ok(height) => height as u32,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Height".to_string(),
error: why.to_string(),
})
}
};
Ok(Resolution::new(width, height))
}
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
pub fn raw_framerate(&self) -> Result<u32, NokhwaError> {
match self.video_capture.get(CAP_PROP_FPS) {
Ok(fps) => Ok(fps as u32),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "Framerate".to_string(),
error: why.to_string(),
}),
}
}
}
impl CaptureBackendTrait for OpenCvCaptureDevice {
fn backend(&self) -> CaptureAPIBackend {
CaptureAPIBackend::OpenCv
}
fn camera_info(&self) -> &CameraInfo {
&self.camera_info
}
fn camera_format(&self) -> CameraFormat {
self.camera_format
}
fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> {
let current_format = self.camera_format;
let is_opened = match self.video_capture.is_opened() {
Ok(opened) => opened,
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: "Is Stream Open".to_string(),
error: why.to_string(),
})
}
};
self.camera_format = new_fmt;
if let Err(why) = set_properties(&mut self.video_capture, new_fmt, &self.camera_location) {
self.camera_format = current_format;
return Err(why);
}
if is_opened {
self.stop_stream()?;
if let Err(why) = self.open_stream() {
return Err(NokhwaError::OpenDeviceError(
self.camera_location.to_string(),
why.to_string(),
));
}
}
Ok(())
}
fn compatible_list_by_resolution(
&mut self,
_fourcc: FrameFormat,
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::OpenCv,
))
}
fn compatible_fourcc(&mut self) -> Result<Vec<FrameFormat>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::OpenCv,
))
}
fn resolution(&self) -> Resolution {
self.raw_resolution()
.unwrap_or_else(|_| Resolution::new(640, 480))
}
fn set_resolution(&mut self, new_res: Resolution) -> Result<(), NokhwaError> {
let mut current_fmt = self.camera_format;
current_fmt.set_resolution(new_res);
self.set_camera_format(current_fmt)
}
fn frame_rate(&self) -> u32 {
self.raw_framerate().unwrap_or(30)
}
fn set_frame_rate(&mut self, new_fps: u32) -> Result<(), NokhwaError> {
let mut current_fmt = self.camera_format;
current_fmt.set_frame_rate(new_fps);
self.set_camera_format(current_fmt)
}
fn frame_format(&self) -> FrameFormat {
self.camera_format.format()
}
fn set_frame_format(&mut self, fourcc: FrameFormat) -> Result<(), NokhwaError> {
let mut current_fmt = self.camera_format;
current_fmt.set_format(fourcc);
self.set_camera_format(current_fmt)
}
fn supported_camera_controls(&self) -> Result<Vec<KnownCameraControls>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn camera_control(&self, _control: KnownCameraControls) -> Result<CameraControl, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn set_camera_control(&mut self, _control: CameraControl) -> Result<(), NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn raw_supported_camera_controls(&self) -> Result<Vec<Box<dyn Any>>, NokhwaError> {
Err(NokhwaError::UnsupportedOperationError(
CaptureAPIBackend::UniversalVideoClass,
))
}
fn raw_camera_control(&self, control: &dyn Any) -> Result<Box<dyn Any>, NokhwaError> {
let id = match control.downcast_ref::<i32>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "OpenCV ID".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
match self.video_capture.get(id) {
Ok(v) => Ok(Box::new(v)),
Err(why) => {
return Err(NokhwaError::GetPropertyError {
property: format!("OpenCV PROP ID {}", id),
error: why.to_string(),
})
}
}
}
fn set_raw_camera_control(
&mut self,
control: &dyn Any,
value: &dyn Any,
) -> Result<(), NokhwaError> {
let id = match control.downcast_ref::<i32>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "OpenCV ID".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
let value = match value.downcast_ref::<f64>() {
Some(id) => *id,
None => {
return Err(NokhwaError::StructureError {
structure: "OpenCV Value".to_string(),
error: "Failed Any Cast".to_string(),
})
}
};
match self.video_capture.set(id, value) {
Ok(b) => {
if b {
return Ok(());
}
Err(NokhwaError::SetPropertyError {
property: format!("OpenCV PROP ID {}", id),
value: value.to_string(),
error: "OpenCV returned false".to_string(),
})
}
Err(why) => Err(NokhwaError::SetPropertyError {
property: format!("OpenCV PROP ID {}", id),
value: value.to_string(),
error: why.to_string(),
}),
}
}
#[allow(clippy::cast_possible_wrap)]
fn open_stream(&mut self) -> Result<(), NokhwaError> {
match self.camera_location.clone() {
CameraIndexType::Index(idx) => {
match self
.video_capture
.open_1(idx as i32, get_api_pref_int() as i32)
{
Ok(_) => {}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
idx.to_string(),
format!("Failed to open device: {}", why),
))
}
}
}
CameraIndexType::IPCamera(ip) => {
match self
.video_capture
.open_file(&*ip, get_api_pref_int() as i32)
{
Ok(_) => {}
Err(why) => {
return Err(NokhwaError::OpenDeviceError(
ip,
format!("Failed to open device: {}", why),
))
}
}
}
};
match self.video_capture.is_opened() {
Ok(open) => {
if open {
return Ok(());
}
Err(NokhwaError::OpenStreamError(
"Stream is not opened after stream open attempt opencv".to_string(),
))
}
Err(why) => Err(NokhwaError::GetPropertyError {
property: "Is Stream Open After Open Stream".to_string(),
error: why.to_string(),
}),
}
}
fn is_stream_open(&self) -> bool {
self.video_capture.is_opened().unwrap_or(false)
}
fn frame(&mut self) -> Result<ImageBuffer<Rgb<u8>, Vec<u8>>, NokhwaError> {
let camera_resolution = self.camera_format.resolution();
let image_data = {
let mut data = self.frame_raw()?.to_vec();
data.resize(
(camera_resolution.width() * camera_resolution.height() * 3) as usize,
0_u8,
);
data
};
let image_buf =
match ImageBuffer::from_vec(
camera_resolution.width(),
camera_resolution.height(),
image_data,
) {
Some(buf) => {
let rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = buf;
rgb
}
None => return Err(NokhwaError::ReadFrameError(
"Imagebuffer is not large enough! This is probably a bug, please report it!"
.to_string(),
)),
};
Ok(image_buf)
}
fn frame_raw(&mut self) -> Result<Cow<[u8]>, NokhwaError> {
let cow = self.raw_frame_vec()?;
Ok(cow)
}
fn stop_stream(&mut self) -> Result<(), NokhwaError> {
match self.video_capture.release() {
Ok(_) => Ok(()),
Err(why) => Err(NokhwaError::StreamShutdownError(why.to_string())),
}
}
}
fn get_api_pref_int() -> u32 {
match std::env::consts::OS {
"linux" => CAP_V4L2 as u32,
"windows" => CAP_MSMF as u32,
"mac" => CAP_AVFOUNDATION as u32,
&_ => CAP_ANY as u32,
}
}
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::unnecessary_wraps)]
fn set_properties(
_vc: &mut VideoCapture,
_camera_format: CameraFormat,
_camera_location: &CameraIndexType,
) -> Result<(), NokhwaError> {
Ok(())
}