diff --git a/libuvc_camera/CMakeLists.txt b/libuvc_camera/CMakeLists.txt index cf7f803aefa588246c2f9f02ab3e8d8d6a5d4862..272cdfa112cd72e4e646087b526bffe4ef506f33 100644 --- a/libuvc_camera/CMakeLists.txt +++ b/libuvc_camera/CMakeLists.txt @@ -21,6 +21,10 @@ set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #uncomment if you have defined services #rosbuild_gensrv() +rosbuild_find_ros_package(dynamic_reconfigure) +include(${dynamic_reconfigure_PACKAGE_PATH}/cmake/cfgbuild.cmake) +gencfg() + #common commands for building c++ executables and libraries #rosbuild_add_library(${PROJECT_NAME} src/example.cpp) #target_link_libraries(${PROJECT_NAME} another_library) @@ -29,6 +33,6 @@ set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #rosbuild_add_executable(example examples/example.cpp) #target_link_libraries(example ${PROJECT_NAME}) -rosbuild_add_executable(libuvc_camera src/libuvc_camera.cpp) -target_link_libraries(libuvc_camera uvc) +rosbuild_add_executable(camera_node src/main.cpp src/camera_driver.cpp) +target_link_libraries(camera_node uvc) diff --git a/libuvc_camera/cfg/UVCCamera.cfg b/libuvc_camera/cfg/UVCCamera.cfg new file mode 100755 index 0000000000000000000000000000000000000000..cbcb06376295b4afd584b994a24232ea478463f5 --- /dev/null +++ b/libuvc_camera/cfg/UVCCamera.cfg @@ -0,0 +1,166 @@ +#! /usr/bin/env python +# Derived from camera1394 cfg + +PACKAGE='libuvc_camera' +import roslib; roslib.load_manifest(PACKAGE) + +from dynamic_reconfigure.parameter_generator import * +from driver_base.msg import SensorLevels + +gen = ParameterGenerator() + +# Name, Type, Reconfiguration level, Description, Default, Min, Max + +gen.add("vendor", str_t, SensorLevels.RECONFIGURE_CLOSE, + "Vendor ID, hex digits (use camera of any vendor if null).", + "") + +gen.add("product", str_t, SensorLevels.RECONFIGURE_CLOSE, + "Product ID, hex digits (use camera of any model if null).", + "") + +gen.add("serial", str_t, SensorLevels.RECONFIGURE_CLOSE, + "Serial number, arbitrary string (use camera of any serial number if null).", + "") + +gen.add("index", int_t, SensorLevels.RECONFIGURE_CLOSE, + "Index into the list of cameras that match the above parameters.", + 0) + +gen.add("width", int_t, SensorLevels.RECONFIGURE_CLOSE, + "Image width.", 640) + +gen.add("height", int_t, SensorLevels.RECONFIGURE_CLOSE, + "Image height.", 480) + +gen.add("frame_rate", double_t, SensorLevels.RECONFIGURE_CLOSE, + "Camera speed, frames per second.", 15.0, 0.1, 1000.0) + +# TODO: video mode -- uncompressed, yuyv, uyvy, rgb, compressed, jpeg, ... + +gen.add("frame_id", str_t, SensorLevels.RECONFIGURE_RUNNING, + "ROS tf frame of reference, resolved with tf_prefix unless absolute.", + "camera") + +# Camera Terminal controls + +scanning_modes = gen.enum([gen.const("Interlaced", int_t, 0, ""), + gen.const("Progressive", int_t, 1, "")], + "Scanning modes") + +gen.add("scanning_mode", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Scanning mode.", 0, 0, 1, + edit_method = scanning_modes) + +auto_exposure_modes = gen.enum([gen.const("Manual", int_t, 0, "Manual exposure, manual iris"), + gen.const("Auto", int_t, 1, "Auto exposure, auto iris"), + gen.const("Shutter_Priority", int_t, 2, "manual exposure, auto iris"), + gen.const("Aperture_Priority", int_t, 3, "auto exposure, manual iris")], + "Auto-exposure modes") + +gen.add("auto_exposure", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Auto exposure mode.", + 1, 0, 3, edit_method = auto_exposure_modes) + +gen.add("auto_exposure_priority", int_t, SensorLevels.RECONFIGURE_RUNNING, + "In auto mode or shutter priority mode, allow the device to vary frame rate.", + 0, 0, 1) + +gen.add("exposure_absolute", double_t, SensorLevels.RECONFIGURE_RUNNING, + "Length of exposure, seconds.", 0., 0.0001, 10.0) + +# TODO: relative exposure time + +gen.add("iris_absolute", double_t, SensorLevels.RECONFIGURE_RUNNING, + "Aperture, f.", 0., 0., 655.35) + +# TODO: relative iris + +gen.add("auto_focus", bool_t, SensorLevels.RECONFIGURE_RUNNING, + "Maintain focus automatically.", False) + +gen.add("focus_absolute", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Absolute focal distance, millimeters.", 0, 0, 65536) + +# TODO: relative focus + +# TODO: zoom + +gen.add("pan_absolute", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Pan (clockwise), arc seconds.", 0, -180*3600, 180*3600) + +gen.add("tilt_absolute", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Tilt (up), arc seconds.", 0, -180*3600, 180*3600) + +# TODO: relative pan/tilt + +gen.add("roll_absolute", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Roll (clockwise), degrees.", 0, -180, 180) + +# TODO: relative roll + +gen.add("privacy", bool_t, SensorLevels.RECONFIGURE_RUNNING, + "Image capture disabled.", False) + +# Processing Unit controls + +gen.add("backlight_compensation", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Backlight compensation, device-dependent (zero for none, increasing compensation above zero).", + 0, 0, 65536) + + +gen.add("brightness", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Brightness, device dependent.", 0, -32768, 32767) + +gen.add("contrast", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Contrast, device dependent.", 0, -32768, 32767) + +gen.add("gain", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Gain, device dependent.", 0, 0, 65536) + +power_line_frequency_modes = gen.enum([gen.const("Disabled", int_t, 0, "Disabled"), + gen.const("Freq_50", int_t, 1, "50 Hz"), + gen.const("Freq_60", int_t, 1, "60 Hz")], + "Power line frequency modes") + +gen.add("power_line_frequency", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Power line frequency anti-flicker processing.", + 0, 0, 2, + edit_method = power_line_frequency_modes) + +gen.add("auto_hue", bool_t, SensorLevels.RECONFIGURE_RUNNING, + "Automatic hue control.", False) + +gen.add("hue", double_t, SensorLevels.RECONFIGURE_RUNNING, + "Hue, degrees.", 0., -180., 180.) + +gen.add("saturation", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Saturation, device dependent (zero for grayscale).", 0, 0, 65536) + +gen.add("sharpness", int_t, SensorLevels.RECONFIGURE_RUNNING, + "Image sharpness, device dependent.", + 0, 0, 65536) + +# TODO: check range definition +gen.add("gamma", double_t, SensorLevels.RECONFIGURE_RUNNING, + "Gamma.", 1.0, 0.01, 5.0) + +gen.add("auto_white_balance", bool_t, SensorLevels.RECONFIGURE_RUNNING, + "Automatic white balance.", False) + +gen.add("white_balance_temperature", int_t, SensorLevels.RECONFIGURE_RUNNING, + "White balance temperature, degrees.", 0, 0, 65536) + +gen.add("white_balance_BU", double_t, SensorLevels.RECONFIGURE_RUNNING, + "Blue or U component of white balance, device-dependent.", + 0, 0, 65536) + +gen.add("white_balance_RV", double_t, SensorLevels.RECONFIGURE_RUNNING, + "Red or V component of white balance, device-dependent.", + 0, 0, 65536) + +# TODO: digital multiplier {,limit} + +# TODO: analog video standard, analog video lock + +exit(gen.generate(PACKAGE, "libuvc_camera", "UVCCamera")) diff --git a/libuvc_camera/include/libuvc_camera/camera_driver.h b/libuvc_camera/include/libuvc_camera/camera_driver.h new file mode 100644 index 0000000000000000000000000000000000000000..a4280795e14ec8a634eb8861c3e3910437f7ff4a --- /dev/null +++ b/libuvc_camera/include/libuvc_camera/camera_driver.h @@ -0,0 +1,59 @@ +#pragma once + +#include <libuvc/libuvc.h> + +#include <ros/ros.h> +#include <image_transport/image_transport.h> +#include <image_transport/camera_publisher.h> +#include <dynamic_reconfigure/server.h> +#include <boost/thread/mutex.hpp> + +#include <libuvc_camera/UVCCameraConfig.h> + +namespace libuvc_camera { + +class CameraDriver { +public: + CameraDriver(ros::NodeHandle nh, ros::NodeHandle priv_nh); + ~CameraDriver(); + + bool Start(); + void Stop(); + +private: + enum State { + kInitial = 0, + kStopped = 1, + kRunning = 2, + }; + + void OpenCamera(UVCCameraConfig &new_config); + void CloseCamera(); + + // Accept a reconfigure request from a client + void ReconfigureCallback(UVCCameraConfig &config, uint32_t level); + // Accept changes in values of automatically updated controls + void AutoControlsCallback(); + static void AutoControlsCallbackAdapter(void *ptr); + // Accept a new image frame from the camera + void ImageCallback(uvc_frame_t *frame); + static void ImageCallbackAdapter(uvc_frame_t *frame, void *ptr); + + ros::NodeHandle nh_, priv_nh_; + + State state_; + boost::mutex mutex_; + + uvc_context_t *ctx_; + uvc_device_t *dev_; + uvc_device_handle_t *devh_; + uvc_frame_t *rgb_frame_; + + image_transport::ImageTransport it_; + image_transport::CameraPublisher cam_pub_; + + dynamic_reconfigure::Server<UVCCameraConfig> config_server_; + UVCCameraConfig config_; +}; + +}; diff --git a/libuvc_camera/manifest.xml b/libuvc_camera/manifest.xml index c953fe8943550780ae00658083877d20dc94af0b..8347f7379e6ee82ea086d2f275c13e463e62af4d 100644 --- a/libuvc_camera/manifest.xml +++ b/libuvc_camera/manifest.xml @@ -9,6 +9,9 @@ <review status="unreviewed" notes=""/> <url>http://ros.org/wiki/libuvc_camera</url> <depend package="roscpp"/> + <depend package="driver_base"/> + <depend package="dynamic_reconfigure"/> + <depend package="image_transport"/> <depend package="sensor_msgs"/> <depend package="libuvc"/> </package> diff --git a/libuvc_camera/src/camera_driver.cpp b/libuvc_camera/src/camera_driver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9c882a342cea000ae6d72a60a30d9de83000c288 --- /dev/null +++ b/libuvc_camera/src/camera_driver.cpp @@ -0,0 +1,208 @@ +#include "libuvc_camera/camera_driver.h" + +#include <ros/ros.h> +#include <sensor_msgs/Image.h> +#include <image_transport/camera_publisher.h> +#include <dynamic_reconfigure/server.h> +#include <dynamic_reconfigure/SensorLevels.h> +#include <libuvc/libuvc.h> + +namespace libuvc_camera { + +CameraDriver::CameraDriver(ros::NodeHandle nh, ros::NodeHandle priv_nh) + : nh_(nh), priv_nh_(priv_nh), + state_(kInitial), + ctx_(NULL), dev_(NULL), devh_(NULL), rgb_frame_(NULL), + it_(nh_), + config_server_(priv_nh_) { + cam_pub_ = it_.advertiseCamera("image_raw", 1, false); +} + +CameraDriver::~CameraDriver() { + if (rgb_frame_) + uvc_free_frame(rgb_frame_); + + if (ctx_) + uvc_exit(ctx_); // Destroys dev_, devh_, etc. +} + +bool CameraDriver::Start() { + uvc_error_t err; + + err = uvc_init(&ctx_, NULL); + + if (err != UVC_SUCCESS) { + uvc_perror(err, "ERROR: uvc_init"); + return false; + } + + state_ = kStopped; + + config_server_.setCallback(boost::bind(&CameraDriver::ReconfigureCallback, this, _1, _2)); + + return state_ == kRunning; +} + +void CameraDriver::Stop() { + boost::mutex::scoped_lock(mutex_); + + if (state_ == kRunning) { + CloseCamera(); + state_ = kInitial; + } +} + +void CameraDriver::ReconfigureCallback(UVCCameraConfig &new_config, uint32_t level) { + boost::mutex::scoped_lock(mutex_); + + if (level & dynamic_reconfigure::SensorLevels::RECONFIGURE_CLOSE) { + if (state_ == kRunning) + CloseCamera(); + } + + if (state_ == kStopped) { + OpenCamera(new_config); + } + + if (state_ == kRunning) { + // TODO: scanning_mode + // TODO: auto_exposure + // TODO: auto_exposure_priority + // TODO: exposure_absolute + // TODO: iris_absolute + // TODO: auto_focus + // TODO: focus_absolute + // TODO: pan_absolute + // TODO: tilt_absolute + // TODO: roll_absolute + // TODO: privacy + // TODO: backlight_compensation + // TODO: brightness + // TODO: contrast + // TODO: gain + // TODO: power_line_frequency + // TODO: auto_hue + // TODO: saturation + // TODO: sharpness + // TODO: gamma + // TODO: auto_white_balance + // TODO: white_balance_temperature + // TODO: white_balance_BU + // TODO: white_balance_RV + } + + config_ = new_config; +} + +void CameraDriver::ImageCallback(uvc_frame_t *frame) { + boost::mutex::scoped_lock(mutex_); + + assert(state_ == kRunning); + assert(rgb_frame_); + + uvc_error_t conv_ret = uvc_any2rgb(frame, rgb_frame_); + + if (conv_ret != UVC_SUCCESS) { + uvc_perror(conv_ret, "Couldn't convert frame to RGB"); + return; + } + + sensor_msgs::Image image; + image.width = config_.width; + image.height = config_.height; + image.encoding = "rgb8"; + image.step = image.width * 3; + image.data.resize(image.step * image.height); + memcpy(&(image.data[0]), rgb_frame_->data, rgb_frame_->data_bytes); + + cam_pub_.publish(image, sensor_msgs::CameraInfo()); +} + +/* static */ void CameraDriver::ImageCallbackAdapter(uvc_frame_t *frame, void *ptr) { + CameraDriver *driver = static_cast<CameraDriver*>(ptr); + + driver->ImageCallback(frame); +} + +void CameraDriver::AutoControlsCallback() { + boost::mutex::scoped_lock(mutex_); + +} + +/* static */ void CameraDriver::AutoControlsCallbackAdapter(void *ptr) { + CameraDriver *driver = static_cast<CameraDriver*>(ptr); + + driver->AutoControlsCallback(); +} + +void CameraDriver::OpenCamera(UVCCameraConfig &new_config) { + assert(state_ == kStopped); + + int vendor_id = strtol(new_config.vendor.c_str(), NULL, 0); + int product_id = strtol(new_config.product.c_str(), NULL, 0); + + ROS_INFO("Opening camera with vendor=0x%x, product=0x%x, serial=\"%s\", index=%d", + vendor_id, product_id, new_config.serial.c_str(), new_config.index); + + uvc_error_t find_err = uvc_find_device( + ctx_, &dev_, + vendor_id, + product_id, + new_config.serial.empty() ? NULL : new_config.serial.c_str()); + + // TODO: index + + if (find_err != UVC_SUCCESS) { + uvc_perror(find_err, "uvc_find_device"); + return; + } + + uvc_error_t open_err = uvc_open(dev_, &devh_); + if (open_err != UVC_SUCCESS) { + uvc_perror(open_err, "uvc_open"); + uvc_unref_device(dev_); + return; + } + + uvc_stream_ctrl_t ctrl; + uvc_error_t mode_err = uvc_get_stream_ctrl_format_size( + devh_, &ctrl, + UVC_COLOR_FORMAT_UNCOMPRESSED, + new_config.width, new_config.height, + new_config.frame_rate); + + if (mode_err != UVC_SUCCESS) { + uvc_perror(mode_err, "uvc_get_stream_ctrl_format_size"); + uvc_close(devh_); + uvc_unref_device(dev_); + return; + } + + uvc_error_t stream_err = uvc_start_iso_streaming(devh_, &ctrl, &CameraDriver::ImageCallbackAdapter, this); + + if (stream_err != UVC_SUCCESS) { + uvc_perror(stream_err, "uvc_start_iso_streaming"); + uvc_close(devh_); + uvc_unref_device(dev_); + return; + } + + rgb_frame_ = uvc_allocate_frame(new_config.width * new_config.height * 3); + assert(rgb_frame_); + + state_ = kRunning; +} + +void CameraDriver::CloseCamera() { + assert(state_ == kRunning); + + uvc_close(devh_); + devh_ = NULL; + + uvc_unref_device(dev_); + dev_ = NULL; + + state_ = kStopped; +} + +}; diff --git a/libuvc_camera/src/libuvc_camera.cpp b/libuvc_camera/src/libuvc_camera.cpp deleted file mode 100644 index 58f52194f0916b5fae63cd77825b97fcbab84eff..0000000000000000000000000000000000000000 --- a/libuvc_camera/src/libuvc_camera.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/** @file test_ros_ctrls.cpp Example/test usage of libuvc */ -#include <ros/ros.h> -#include <sensor_msgs/Image.h> -#include "libuvc/libuvc.h" - -using std::string; - -ros::Publisher pub; - -void cb(uvc_frame_t *frame) { - static uvc_frame_t *rgb_frame = NULL; - uvc_error_t uvc_ret; - - if (!rgb_frame) - rgb_frame = uvc_allocate_frame(frame->width * frame->height * 3); - - uvc_ret = uvc_any2rgb(frame, rgb_frame); - - if (uvc_ret) { - uvc_perror(uvc_ret, "Couldn't convert frame to RGB"); - return; - } - - sensor_msgs::Image image; - image.width = rgb_frame->width; - image.height = rgb_frame->height; - image.encoding = "rgb8"; - image.step = image.width * 3; - image.data.resize(image.step * image.height); - memcpy(&(image.data[0]), rgb_frame->data, rgb_frame->data_bytes); - - pub.publish(image); -} - -int main (int argc, char **argv) { - ros::init(argc, argv, "libuvc_camera"); - ros::NodeHandle nh; - ros::NodeHandle priv_nh("~"); - - pub = nh.advertise<sensor_msgs::Image>("image_raw", 1); - - uvc_context_t *ctx; - uvc_error_t res; - uvc_device_t *dev; - uvc_device_handle_t *devh; - uvc_stream_ctrl_t ctrl; - - res = uvc_init(&ctx, NULL); - - if (res < 0) { - uvc_perror(res, "uvc_init"); - return res; - } - - puts("UVC initialized"); - - int vendor_id; - string vendor_id_str; - priv_nh.getParam("vendor", vendor_id_str); - vendor_id = strtol(vendor_id_str.c_str(), NULL, 0); - - int product_id; - string product_id_str; - priv_nh.getParam("product", product_id_str); - product_id = strtol(product_id_str.c_str(), NULL, 0); - - string serial_num; - priv_nh.getParam("serial", serial_num); - - printf("pid: %d\n", product_id); - printf("vid: %d\n", vendor_id); - - res = uvc_find_device( - ctx, &dev, - vendor_id, - product_id, - serial_num.empty() ? NULL : serial_num.c_str()); - - if (res < 0) { - uvc_perror(res, "uvc_find_device"); - } else { - res = uvc_open(dev, &devh); - if (res < 0) { - uvc_perror(res, "uvc_open"); - } else { - puts("Device opened"); - - uvc_print_diag(devh, stderr); - - res = uvc_get_stream_ctrl_format_size( - devh, &ctrl, - UVC_COLOR_FORMAT_UNCOMPRESSED, 640, 480, 30); - - uvc_print_stream_ctrl(&ctrl, stderr); - - if (res < 0) { - uvc_perror(res, "get_mode"); - } else { - res = uvc_start_iso_streaming(devh, &ctrl, cb); - - if (res < 0) { - uvc_perror(res, "start_streaming"); - } else { - puts("Streaming..."); - ros::spin(); - uvc_stop_streaming(devh); - } - } - - uvc_close(devh); - puts("Device closed"); - } - } - - uvc_exit(ctx); - puts("UVC exited"); - - return 0; -} - diff --git a/libuvc_camera/src/main.cpp b/libuvc_camera/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..48bb9175457f75accde7add334d7de4203695406 --- /dev/null +++ b/libuvc_camera/src/main.cpp @@ -0,0 +1,20 @@ +#include <ros/ros.h> + +#include "libuvc_camera/camera_driver.h" + +int main (int argc, char **argv) { + ros::init(argc, argv, "libuvc_camera"); + ros::NodeHandle nh; + ros::NodeHandle priv_nh("~"); + + libuvc_camera::CameraDriver driver(nh, priv_nh); + + if (!driver.Start()) + return -1; + + ros::spin(); + + driver.Stop(); + + return 0; +}