Files
rtabmap/corelib/src/camera/CameraOpenNI2.cpp
T
matlabbe d9f3337f97 OpenCV 5 support (#1732)
* OpenCV 5 support

* RTABMapConfig.cmake, guard from including stereoRectifyFisheye.h

* unified opencv components at the same place

* fixing android build

* Confirmed stereo calibration with fisheye works. Fix camera start/top progress dialog not drawn. Opencv >=4.7 using new opencv's ArucoDetector class.

* pinning opencv for downstream apps

* Avoid changing object/image points between fisheye calibration

* Fixed stereo calib diverging when recalibrating same data

* removed not needed opencv c api

* fixing depthai build on opencv5

* bumped version

* Adding ci opencv5 with homebrew

* fixed ci script

* Fixed OptimizerCeres build with opencv5
2026-07-29 22:48:39 -07:00

612 lines
19 KiB
C++

/*
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Universite de Sherbrooke nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <rtabmap/core/camera/CameraOpenNI2.h>
#include <rtabmap/utilite/UTimer.h>
#include <rtabmap/utilite/UFile.h>
#include <rtabmap/utilite/UThreadC.h>
#include <rtabmap/core/util2d.h>
#ifdef RTABMAP_OPENNI2
#include <OniVersion.h>
#include <OpenNI.h>
#endif
namespace rtabmap
{
bool CameraOpenNI2::available()
{
#ifdef RTABMAP_OPENNI2
return true;
#else
return false;
#endif
}
bool CameraOpenNI2::exposureGainAvailable()
{
#if ONI_VERSION_MAJOR > 2 || (ONI_VERSION_MAJOR==2 && ONI_VERSION_MINOR >= 2)
return true;
#else
return false;
#endif
}
CameraOpenNI2::CameraOpenNI2(
const std::string & deviceId,
Type type,
float imageRate,
const rtabmap::Transform & localTransform) :
Camera(imageRate, localTransform)
#ifdef RTABMAP_OPENNI2
,
_type(type),
_device(new openni::Device()),
_color(new openni::VideoStream()),
_depth(new openni::VideoStream()),
_depthFx(0.0f),
_depthFy(0.0f),
_deviceId(deviceId),
_openNI2StampsAndIDsUsed(false),
_depthHShift(0),
_depthVShift(0),
_depthDecimation(1)
#endif
{
}
CameraOpenNI2::~CameraOpenNI2()
{
#ifdef RTABMAP_OPENNI2
_color->stop();
_color->destroy();
_depth->stop();
_depth->destroy();
_device->close();
openni::OpenNI::shutdown();
delete _device;
delete _color;
delete _depth;
#endif
}
bool CameraOpenNI2::setAutoWhiteBalance(bool enabled)
{
#ifdef RTABMAP_OPENNI2
if(_color && _color->getCameraSettings())
{
return _color->getCameraSettings()->setAutoWhiteBalanceEnabled(enabled) == openni::STATUS_OK;
}
#else
UERROR("CameraOpenNI2: RTAB-Map is not built with OpenNI2 support!");
#endif
return false;
}
bool CameraOpenNI2::setAutoExposure(bool enabled)
{
#ifdef RTABMAP_OPENNI2
if(_color && _color->getCameraSettings())
{
return _color->getCameraSettings()->setAutoExposureEnabled(enabled) == openni::STATUS_OK;
}
#else
UERROR("CameraOpenNI2: RTAB-Map is not built with OpenNI2 support!");
#endif
return false;
}
bool CameraOpenNI2::setExposure(int value)
{
#ifdef RTABMAP_OPENNI2
#if ONI_VERSION_MAJOR > 2 || (ONI_VERSION_MAJOR==2 && ONI_VERSION_MINOR >= 2)
if(_color && _color->getCameraSettings())
{
return _color->getCameraSettings()->setExposure(value) == openni::STATUS_OK;
}
#else
UERROR("CameraOpenNI2: OpenNI >= 2.2 required to use this method.");
#endif
#else
UERROR("CameraOpenNI2: RTAB-Map is not built with OpenNI2 support!");
#endif
return false;
}
bool CameraOpenNI2::setGain(int value)
{
#ifdef RTABMAP_OPENNI2
#if ONI_VERSION_MAJOR > 2 || (ONI_VERSION_MAJOR==2 && ONI_VERSION_MINOR >= 2)
if(_color && _color->getCameraSettings())
{
return _color->getCameraSettings()->setGain(value) == openni::STATUS_OK;
}
#else
UERROR("CameraOpenNI2: OpenNI >= 2.2 required to use this method.");
#endif
#else
UERROR("CameraOpenNI2: RTAB-Map is not built with OpenNI2 support!");
#endif
return false;
}
bool CameraOpenNI2::setMirroring(bool enabled)
{
#ifdef RTABMAP_OPENNI2
if(_color->isValid() && _depth->isValid())
{
return _depth->setMirroringEnabled(enabled) == openni::STATUS_OK &&
_color->setMirroringEnabled(enabled) == openni::STATUS_OK;
}
#endif
return false;
}
void CameraOpenNI2::setOpenNI2StampsAndIDsUsed(bool used)
{
#ifdef RTABMAP_OPENNI2
_openNI2StampsAndIDsUsed = used;
#endif
}
void CameraOpenNI2::setIRDepthShift(int horizontal, int vertical)
{
#ifdef RTABMAP_OPENNI2
_depthHShift = horizontal;
_depthVShift = vertical;
#endif
}
void CameraOpenNI2::setDepthDecimation(int decimation)
{
#ifdef RTABMAP_OPENNI2
UASSERT(decimation >= 1);
_depthDecimation = decimation;
#endif
}
bool CameraOpenNI2::init(const std::string & calibrationFolder, const std::string & cameraName)
{
#ifdef RTABMAP_OPENNI2
openni::OpenNI::initialize();
openni::Array<openni::DeviceInfo> devices;
openni::OpenNI::enumerateDevices(&devices);
for(int i=0; i<devices.getSize(); ++i)
{
UINFO("Device %d: Name=%s URI=%s Vendor=%s",
i,
devices[i].getName(),
devices[i].getUri(),
devices[i].getVendor());
}
if(_deviceId.empty() && devices.getSize() == 0)
{
UERROR("CameraOpenNI2: No device detected!");
return false;
}
openni::Status error = _device->open(_deviceId.empty()?openni::ANY_DEVICE:_deviceId.c_str());
if(error != openni::STATUS_OK)
{
if(!_deviceId.empty())
{
UERROR("CameraOpenNI2: Cannot open device \"%s\" (error=%d).", _deviceId.c_str(), error);
}
else
{
#ifdef _WIN32
UERROR("CameraOpenNI2: Cannot open device \"%s\" (error=%d).", devices[0].getName(), error);
#else
UERROR("CameraOpenNI2: Cannot open device \"%s\" (error=%d). Verify if \"%s\" is in udev rules: \"/lib/udev/rules.d/40-libopenni2-0.rules\". If not, add it and reboot.", devices[0].getName(), error, devices[0].getUri());
#endif
}
_device->close();
openni::OpenNI::shutdown();
return false;
}
// look for calibration files
_stereoModel = StereoCameraModel();
bool hardwareRegistration = true;
if(!calibrationFolder.empty())
{
// we need the serial
std::string calibrationName = _device->getDeviceInfo().getName();
if(!cameraName.empty())
{
calibrationName = cameraName;
}
_stereoModel.setName(calibrationName, "depth", "rgb");
hardwareRegistration = !_stereoModel.load(calibrationFolder, calibrationName, false);
if(_type != kTypeColorDepth)
{
hardwareRegistration = false;
}
if((_type != kTypeColorDepth && !_stereoModel.left().isValidForRectification()) ||
(_type == kTypeColorDepth && !_stereoModel.right().isValidForRectification()))
{
UWARN("Missing calibration files for camera \"%s\" in \"%s\" folder, default calibration used.",
calibrationName.c_str(), calibrationFolder.c_str());
}
else if(_type == kTypeColorDepth && _stereoModel.right().isValidForRectification() && hardwareRegistration)
{
UWARN("Missing extrinsic calibration file for camera \"%s\" in \"%s\" folder, default registration is used even if rgb is rectified!",
calibrationName.c_str(), calibrationFolder.c_str());
}
else if(_type == kTypeColorDepth && _stereoModel.right().isValidForRectification() && !hardwareRegistration)
{
UINFO("Custom calibration files for \"%s\" were found in \"%s\" folder. To use "
"factory calibration, remove the corresponding files from that directory.", calibrationName.c_str(), calibrationFolder.c_str());
}
}
if(UFile::getExtension(_deviceId).compare("oni")==0)
{
if(_device->getPlaybackControl() &&
_device->getPlaybackControl()->setRepeatEnabled(false) != openni::STATUS_OK)
{
UERROR("CameraOpenNI2: Cannot set repeat mode to false.");
_device->close();
openni::OpenNI::shutdown();
return false;
}
}
else if(_type==kTypeColorDepth && hardwareRegistration &&
!_device->isImageRegistrationModeSupported(openni::IMAGE_REGISTRATION_DEPTH_TO_COLOR))
{
UERROR("CameraOpenNI2: Device doesn't support depth/color registration.");
_device->close();
openni::OpenNI::shutdown();
return false;
}
if(_device->getSensorInfo(openni::SENSOR_DEPTH) == NULL ||
_device->getSensorInfo(_type==kTypeColorDepth?openni::SENSOR_COLOR:openni::SENSOR_IR) == NULL)
{
UERROR("CameraOpenNI2: Cannot get sensor info for depth and %s.", _type==kTypeColorDepth?"color":"ir");
_device->close();
openni::OpenNI::shutdown();
return false;
}
if(_depth->create(*_device, openni::SENSOR_DEPTH) != openni::STATUS_OK)
{
UERROR("CameraOpenNI2: Cannot create depth stream.");
_device->close();
openni::OpenNI::shutdown();
return false;
}
if(_color->create(*_device, _type==kTypeColorDepth?openni::SENSOR_COLOR:openni::SENSOR_IR) != openni::STATUS_OK)
{
UERROR("CameraOpenNI2: Cannot create %s stream.", _type==kTypeColorDepth?"color":"ir");
_depth->destroy();
_device->close();
openni::OpenNI::shutdown();
return false;
}
if(_type==kTypeColorDepth && hardwareRegistration &&
_device->setImageRegistrationMode(openni::IMAGE_REGISTRATION_DEPTH_TO_COLOR ) != openni::STATUS_OK)
{
UERROR("CameraOpenNI2: Failed to set depth/color registration.");
}
if (_device->setDepthColorSyncEnabled(true) != openni::STATUS_OK)
{
UERROR("CameraOpenNI2: Failed to set depth color sync");
}
_depth->setMirroringEnabled(false);
_color->setMirroringEnabled(false);
const openni::Array<openni::VideoMode>& depthVideoModes = _depth->getSensorInfo().getSupportedVideoModes();
for(int i=0; i<depthVideoModes.getSize(); ++i)
{
UINFO("CameraOpenNI2: Depth video mode %d: fps=%d, pixel=%d, w=%d, h=%d",
i,
depthVideoModes[i].getFps(),
depthVideoModes[i].getPixelFormat(),
depthVideoModes[i].getResolutionX(),
depthVideoModes[i].getResolutionY());
}
const openni::Array<openni::VideoMode>& colorVideoModes = _color->getSensorInfo().getSupportedVideoModes();
for(int i=0; i<colorVideoModes.getSize(); ++i)
{
UINFO("CameraOpenNI2: %s video mode %d: fps=%d, pixel=%d, w=%d, h=%d",
_type==kTypeColorDepth?"color":"ir",
i,
colorVideoModes[i].getFps(),
colorVideoModes[i].getPixelFormat(),
colorVideoModes[i].getResolutionX(),
colorVideoModes[i].getResolutionY());
}
openni::VideoMode mMode;
mMode.setFps(30);
mMode.setResolution(640,480);
mMode.setPixelFormat(openni::PIXEL_FORMAT_DEPTH_1_MM);
_depth->setVideoMode(mMode);
openni::VideoMode mModeColor;
mModeColor.setFps(30);
mModeColor.setResolution(640,480);
mModeColor.setPixelFormat(openni::PIXEL_FORMAT_RGB888);
_color->setVideoMode(mModeColor);
UINFO("CameraOpenNI2: Using depth video mode: fps=%d, pixel=%d, w=%d, h=%d, H-FOV=%f rad, V-FOV=%f rad",
_depth->getVideoMode().getFps(),
_depth->getVideoMode().getPixelFormat(),
_depth->getVideoMode().getResolutionX(),
_depth->getVideoMode().getResolutionY(),
_depth->getHorizontalFieldOfView(),
_depth->getVerticalFieldOfView());
UINFO("CameraOpenNI2: Using %s video mode: fps=%d, pixel=%d, w=%d, h=%d, H-FOV=%f rad, V-FOV=%f rad",
_type==kTypeColorDepth?"color":"ir",
_color->getVideoMode().getFps(),
_color->getVideoMode().getPixelFormat(),
_color->getVideoMode().getResolutionX(),
_color->getVideoMode().getResolutionY(),
_color->getHorizontalFieldOfView(),
_color->getVerticalFieldOfView());
if(_depth->getVideoMode().getResolutionX() != 640 ||
_depth->getVideoMode().getResolutionY() != 480 ||
_depth->getVideoMode().getPixelFormat() != openni::PIXEL_FORMAT_DEPTH_1_MM)
{
UERROR("Could not set depth format to 640x480 pixel=%d(mm)!",
openni::PIXEL_FORMAT_DEPTH_1_MM);
_depth->destroy();
_color->destroy();
_device->close();
openni::OpenNI::shutdown();
return false;
}
if(_color->getVideoMode().getResolutionX() != 640 ||
_color->getVideoMode().getResolutionY() != 480 ||
_color->getVideoMode().getPixelFormat() != openni::PIXEL_FORMAT_RGB888)
{
UERROR("Could not set %s format to 640x480 pixel=%d!",
_type==kTypeColorDepth?"color":"ir",
openni::PIXEL_FORMAT_RGB888);
_depth->destroy();
_color->destroy();
_device->close();
openni::OpenNI::shutdown();
return false;
}
if(_color->getCameraSettings())
{
UINFO("CameraOpenNI2: AutoWhiteBalanceEnabled = %d", _color->getCameraSettings()->getAutoWhiteBalanceEnabled()?1:0);
UINFO("CameraOpenNI2: AutoExposureEnabled = %d", _color->getCameraSettings()->getAutoExposureEnabled()?1:0);
#if ONI_VERSION_MAJOR > 2 || (ONI_VERSION_MAJOR==2 && ONI_VERSION_MINOR >= 2)
UINFO("CameraOpenNI2: Exposure = %d", _color->getCameraSettings()->getExposure());
UINFO("CameraOpenNI2: GAIN = %d", _color->getCameraSettings()->getGain());
#endif
}
if(_type==kTypeColorDepth && hardwareRegistration)
{
_depthFx = float(_color->getVideoMode().getResolutionX()/2) / std::tan(_color->getHorizontalFieldOfView()/2.0f);
_depthFy = float(_color->getVideoMode().getResolutionY()/2) / std::tan(_color->getVerticalFieldOfView()/2.0f);
}
else
{
_depthFx = float(_depth->getVideoMode().getResolutionX()/2) / std::tan(_depth->getHorizontalFieldOfView()/2.0f);
_depthFy = float(_depth->getVideoMode().getResolutionY()/2) / std::tan(_depth->getVerticalFieldOfView()/2.0f);
}
UINFO("depth fx=%f fy=%f", _depthFx, _depthFy);
if(_type == kTypeIR)
{
UWARN("With type IR-only, depth stream will not be started");
}
if((_type != kTypeIR && _depth->start() != openni::STATUS_OK) ||
_color->start() != openni::STATUS_OK)
{
UERROR("CameraOpenNI2: Cannot start depth and/or color streams.");
_depth->stop();
_color->stop();
_depth->destroy();
_color->destroy();
_device->close();
openni::OpenNI::shutdown();
return false;
}
uSleep(3000); // just to make sure the sensor is correctly initialized and exposure is set
return true;
#else
UERROR("CameraOpenNI2: RTAB-Map is not built with OpenNI2 support!");
return false;
#endif
}
bool CameraOpenNI2::isCalibrated() const
{
return true;
}
std::string CameraOpenNI2::getSerial() const
{
#ifdef RTABMAP_OPENNI2
if(_device)
{
return _device->getDeviceInfo().getName();
}
#endif
return "";
}
SensorData CameraOpenNI2::captureImage(SensorCaptureInfo * info)
{
SensorData data;
#ifdef RTABMAP_OPENNI2
int readyStream = -1;
if(_device->isValid() &&
_depth->isValid() &&
_color->isValid() &&
_device->getSensorInfo(openni::SENSOR_DEPTH) != NULL &&
_device->getSensorInfo(_type==kTypeColorDepth?openni::SENSOR_COLOR:openni::SENSOR_IR) != NULL)
{
openni::VideoStream* depthStream[] = {_depth};
openni::VideoStream* colorStream[] = {_color};
if((_type != kTypeIR && openni::OpenNI::waitForAnyStream(depthStream, 1, &readyStream, 5000) != openni::STATUS_OK) ||
openni::OpenNI::waitForAnyStream(colorStream, 1, &readyStream, 5000) != openni::STATUS_OK)
{
UWARN("No frames received since the last 5 seconds, end of stream is reached!");
}
else
{
openni::VideoFrameRef depthFrame, colorFrame;
if(_type != kTypeIR)
{
_depth->readFrame(&depthFrame);
}
_color->readFrame(&colorFrame);
cv::Mat depth, rgb;
if((_type == kTypeIR || depthFrame.isValid()) && colorFrame.isValid())
{
int h,w;
if(_type != kTypeIR)
{
h=depthFrame.getHeight();
w=depthFrame.getWidth();
depth = cv::Mat(h, w, CV_16U, (void*)depthFrame.getData()).clone();
}
h=colorFrame.getHeight();
w=colorFrame.getWidth();
cv::Mat tmp(h, w, CV_8UC3, (void *)colorFrame.getData());
if(_type==kTypeColorDepth)
{
cv::cvtColor(tmp, rgb, cv::COLOR_RGB2BGR);
}
else // IR
{
rgb = tmp.clone();
}
}
UASSERT(_depthFx != 0.0f && _depthFy != 0.0f);
if(!rgb.empty() && (_type == kTypeIR || !depth.empty()))
{
// default calibration
CameraModel model(
_depthFx, //fx
_depthFy, //fy
float(rgb.cols/2) - 0.5f, //cx
float(rgb.rows/2) - 0.5f, //cy
this->getLocalTransform(),
0,
rgb.size());
if(_type==kTypeColorDepth)
{
if (_depthHShift != 0 || _depthVShift != 0)
{
cv::Mat out = cv::Mat::zeros(depth.size(), depth.type());
depth(cv::Rect(
_depthHShift>0?_depthHShift:0,
_depthVShift>0?_depthVShift:0,
depth.cols - abs(_depthHShift),
depth.rows - abs(_depthVShift))).copyTo(
out(cv::Rect(
_depthHShift<0?-_depthHShift:0,
_depthVShift<0?-_depthVShift:0,
depth.cols - abs(_depthHShift),
depth.rows - abs(_depthVShift))));
depth = out;
}
if(_stereoModel.right().isValidForRectification())
{
rgb = _stereoModel.right().rectifyImage(rgb);
model = _stereoModel.right();
if(_stereoModel.left().isValidForRectification() && !_stereoModel.stereoTransform().isNull())
{
depth = _stereoModel.left().rectifyImage(depth, 0);
CameraModel depthModel = _stereoModel.left().scaled(1.0 / double(_depthDecimation));
depth = util2d::decimate(depth, _depthDecimation);
depth = util2d::registerDepth(depth, depthModel.K(), rgb.size()/_depthDecimation, _stereoModel.right().scaled(1.0/double(_depthDecimation)).K(), _stereoModel.stereoTransform());
}
else if (_depthDecimation > 1)
{
depth = util2d::decimate(depth, _depthDecimation);
}
}
else if (_depthDecimation > 1)
{
depth = util2d::decimate(depth, _depthDecimation);
}
}
else // IR
{
if(_stereoModel.left().isValidForRectification())
{
rgb = _stereoModel.left().rectifyImage(rgb);
if(_type!=kTypeIR)
{
depth = _stereoModel.left().rectifyImage(depth, 0);
}
model = _stereoModel.left();
}
}
model.setLocalTransform(this->getLocalTransform());
if(_openNI2StampsAndIDsUsed)
{
data = SensorData(rgb, depth, model, depthFrame.getFrameIndex(), double(depthFrame.getTimestamp()) / 1000000.0);
}
else
{
data = SensorData(rgb, depth, model, this->getNextSeqID(), UTimer::now());
}
}
}
}
else
{
ULOGGER_WARN("The camera must be initialized before requesting an image.");
}
#else
UERROR("CameraOpenNI2: RTAB-Map is not built with OpenNI2 support!");
#endif
return data;
}
} // namespace rtabmap