Skip to content

Commit 84c748e

Browse files
authored
Extend wide angle cameras to support L8 and L16 image formats (#1097)
Signed-off-by: Ian Chen <[email protected]>
1 parent 7765666 commit 84c748e

File tree

3 files changed

+209
-29
lines changed

3 files changed

+209
-29
lines changed

ogre/src/OgreWideAngleCamera.cc

+4-4
Original file line numberDiff line numberDiff line change
@@ -798,11 +798,11 @@ void OgreWideAngleCamera::PostRender()
798798
PixelFormat format = this->ImageFormat();
799799
unsigned int channelCount = PixelUtil::ChannelCount(format);
800800
unsigned int bytesPerChannel = PixelUtil::BytesPerChannel(format);
801-
801+
unsigned int bufferSize = len * channelCount * bytesPerChannel;
802802
if (!this->dataPtr->wideAngleImage)
803-
this->dataPtr->wideAngleImage = new unsigned char[len * channelCount];
803+
this->dataPtr->wideAngleImage = new unsigned char[bufferSize];
804804
if (!this->dataPtr->imageBuffer)
805-
this->dataPtr->imageBuffer = new unsigned char[len * channelCount];
805+
this->dataPtr->imageBuffer = new unsigned char[bufferSize];
806806

807807
// get image data
808808
Ogre::RenderTarget *rt =
@@ -813,7 +813,7 @@ void OgreWideAngleCamera::PostRender()
813813

814814
// fill image data
815815
memcpy(this->dataPtr->wideAngleImage, this->dataPtr->imageBuffer,
816-
height*width*channelCount*bytesPerChannel);
816+
bufferSize);
817817

818818
this->dataPtr->newImageFrame(
819819
this->dataPtr->wideAngleImage, width, height, channelCount,

ogre2/src/Ogre2WideAngleCamera.cc

+59-25
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ class gz::rendering::Ogre2WideAngleCamera::Implementation
161161
/// changed
162162
public: bool backgroundMaterialDirty = false;
163163

164+
/// \brief Destination image data - used if image format is not rgb
165+
/// and needs to be converted to another format.
166+
public: std::unique_ptr<unsigned char []> dstImgData;
167+
164168
explicit Implementation(gz::rendering::Ogre2WideAngleCamera &_owner) :
165169
workspaceListener(_owner)
166170
{
@@ -1261,40 +1265,70 @@ void Ogre2WideAngleCamera::PostRender()
12611265
if (this->dataPtr->newImageFrame.ConnectionCount() <= 0u)
12621266
return;
12631267

1268+
PixelFormat format = this->ImageFormat();
12641269
const unsigned int width = this->ImageWidth();
12651270
const unsigned int height = this->ImageHeight();
1271+
unsigned int channelCount = PixelUtil::ChannelCount(format);
1272+
unsigned int bytesPerChannel = PixelUtil::BytesPerChannel(format);
12661273

12671274
// blit data from gpu to cpu
1268-
Ogre::Image2 image;
1269-
image.convertFromTexture(this->dataPtr->ogreStitchTexture[kStichFinalTexture],
1270-
0u, 0u);
1271-
Ogre::TextureBox box = image.getData(0u);
1272-
1273-
// Convert in-place from RGBA32 to RGB24 reusing the same memory region.
1274-
// The data contained will no longer be meaningful to Image2, but that
1275-
// class will no longer manipulate that data. We also store it contiguously
1276-
// (which is what gazebo expects), instead of aligning rows to 4 bytes like
1277-
// Ogre does. This saves RAM and lots of bandwidth.
1278-
uint8_t *RESTRICT_ALIAS rgb24 =
1279-
reinterpret_cast<uint8_t * RESTRICT_ALIAS>(box.data);
1280-
for (size_t y = 0; y < box.height; ++y)
1275+
Ogre::TextureGpu *texture =
1276+
this->dataPtr->ogreStitchTexture[kStichFinalTexture];
1277+
void *rawData = nullptr;
1278+
Ogre::Image2 ogreImage;
1279+
if (format == PF_R8G8B8)
12811280
{
1282-
uint8_t *RESTRICT_ALIAS rgba32 =
1283-
reinterpret_cast<uint8_t * RESTRICT_ALIAS>(box.at(0u, y, 0u));
1284-
for (size_t x = 0; x < box.width; ++x)
1281+
ogreImage.convertFromTexture(texture, 0u, 0u);
1282+
Ogre::TextureBox box = ogreImage.getData(0u);
1283+
1284+
// Convert in-place from RGBA32 to RGB24 reusing the same memory region.
1285+
// The data contained will no longer be meaningful to Image2, but that
1286+
// class will no longer manipulate that data. We also store it contiguously
1287+
// (which is what gazebo expects), instead of aligning rows to 4 bytes like
1288+
// Ogre does. This saves RAM and lots of bandwidth.
1289+
uint8_t *RESTRICT_ALIAS rgb24 =
1290+
reinterpret_cast<uint8_t * RESTRICT_ALIAS>(box.data);
1291+
for (size_t y = 0; y < box.height; ++y)
12851292
{
1286-
*rgb24++ = *rgba32++;
1287-
*rgb24++ = *rgba32++;
1288-
*rgb24++ = *rgba32++;
1289-
++rgba32;
1293+
uint8_t *RESTRICT_ALIAS rgba32 =
1294+
reinterpret_cast<uint8_t * RESTRICT_ALIAS>(box.at(0u, y, 0u));
1295+
for (size_t x = 0; x < box.width; ++x)
1296+
{
1297+
*rgb24++ = *rgba32++;
1298+
*rgb24++ = *rgba32++;
1299+
*rgb24++ = *rgba32++;
1300+
++rgba32;
1301+
}
12901302
}
1303+
rawData = box.data;
12911304
}
1292-
1293-
PixelFormat format = this->ImageFormat();
1294-
unsigned int channelCount = PixelUtil::ChannelCount(format);
1295-
this->dataPtr->newImageFrame(reinterpret_cast<uint8_t *>(box.data), width,
1305+
else
1306+
{
1307+
// convert to destination format
1308+
Ogre::PixelFormatGpu dstOgrePf = Ogre2Conversions::Convert(format);
1309+
Ogre::TextureBox dstBox(
1310+
texture->getInternalWidth(), texture->getInternalHeight(),
1311+
texture->getDepth(), texture->getNumSlices(),
1312+
static_cast<uint32_t>(
1313+
Ogre::PixelFormatGpuUtils::getBytesPerPixel(dstOgrePf)),
1314+
static_cast<uint32_t>(Ogre::PixelFormatGpuUtils::getSizeBytes(
1315+
texture->getInternalWidth(), 1u, 1u, 1u, dstOgrePf, 1u)),
1316+
static_cast<uint32_t>(Ogre::PixelFormatGpuUtils::getSizeBytes(
1317+
texture->getInternalWidth(), texture->getInternalHeight(), 1u, 1u,
1318+
dstOgrePf, 1u)));
1319+
if (!this->dataPtr->dstImgData)
1320+
{
1321+
this->dataPtr->dstImgData = std::make_unique<unsigned char []>(
1322+
width * height * channelCount * bytesPerChannel);
1323+
}
1324+
dstBox.data = this->dataPtr->dstImgData.get();
1325+
Ogre::Image2::copyContentsToMemory(texture, texture->getEmptyBox(0u),
1326+
dstBox, dstOgrePf);
1327+
rawData = dstBox.data;
1328+
}
1329+
this->dataPtr->newImageFrame(reinterpret_cast<uint8_t *>(rawData), width,
12961330
height, channelCount,
1297-
PixelUtil::Name(this->ImageFormat()));
1331+
PixelUtil::Name(format));
12981332

12991333
// Uncomment to debug wide angle cameraoutput
13001334
// gzdbg << "wxh: " << width << " x " << height << std::endl;

test/integration/wide_angle_camera.cc

+146
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ std::mutex g_mutex;
4242

4343
/// \brief WideAngle buffer
4444
unsigned char *g_buffer = nullptr;
45+
unsigned char *g_bufferL8 = nullptr;
46+
unsigned char *g_bufferL16 = nullptr;
4547

4648
/// \brief counter of received wideAngle msgs
4749
int g_counter = 0;
@@ -68,6 +70,41 @@ void OnNewWideAngleFrame(const unsigned char *_data,
6870
g_mutex.unlock();
6971
}
7072

73+
//////////////////////////////////////////////////
74+
/// \brief callback to get the wide angle camera image data
75+
void OnNewWideAngleFrameMono(const unsigned char *_data,
76+
unsigned int _width, unsigned int _height,
77+
unsigned int _channels,
78+
const std::string &_format)
79+
{
80+
g_mutex.lock();
81+
82+
unsigned int bytesPerChannel = 0u;
83+
unsigned int bufferSize = 0u;
84+
if (_format == "L8")
85+
{
86+
bytesPerChannel = 1u;
87+
bufferSize = _width * _height * _channels * bytesPerChannel;
88+
if (!g_bufferL8)
89+
g_bufferL8 = new unsigned char[bufferSize];
90+
memcpy(g_bufferL8, _data, bufferSize);
91+
}
92+
else if (_format == "L16")
93+
{
94+
bytesPerChannel = 2u;
95+
bufferSize = _width * _height * _channels * bytesPerChannel;
96+
if (!g_bufferL16)
97+
g_bufferL16 = new unsigned char[bufferSize];
98+
memcpy(g_bufferL16, _data, bufferSize);
99+
}
100+
101+
ASSERT_NE(0u, bytesPerChannel);
102+
ASSERT_NE(0u, bufferSize);
103+
104+
g_counter++;
105+
g_mutex.unlock();
106+
}
107+
71108
//////////////////////////////////////////////////
72109
TEST_F(WideAngleCameraTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(WideAngleCamera))
73110
{
@@ -339,6 +376,8 @@ TEST_F(WideAngleCameraTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(WideAngleCamera))
339376
EXPECT_EQ(bSumQ[1][0], bSumQ[0][1]);
340377

341378
// Clean up
379+
delete [] g_buffer;
380+
g_buffer = nullptr;
342381
engine->DestroyScene(scene);
343382
}
344383

@@ -460,3 +499,110 @@ TEST_F(WideAngleCameraTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(Projection))
460499

461500
ASSERT_EQ(1u, camera.use_count());
462501
}
502+
503+
//////////////////////////////////////////////////
504+
TEST_F(WideAngleCameraTest,
505+
GZ_UTILS_TEST_DISABLED_ON_WIN32(WideAngleCameraMono))
506+
{
507+
CHECK_UNSUPPORTED_ENGINE("optix");
508+
509+
gz::rendering::ScenePtr scene = engine->CreateScene("scene");
510+
ASSERT_NE(nullptr, scene);
511+
scene->SetAmbientLight(1.0, 1.0, 1.0);
512+
scene->SetBackgroundColor(0.2, 0.2, 0.2);
513+
514+
rendering::VisualPtr root = scene->RootVisual();
515+
516+
unsigned int width = 20u;
517+
unsigned int height = 20u;
518+
519+
// Create Wide Angle camera
520+
auto cameraL8 = scene->CreateWideAngleCamera("WideAngleCameraL8");
521+
ASSERT_NE(cameraL8, nullptr);
522+
cameraL8->SetImageFormat(PF_L8);
523+
524+
auto cameraL16 = scene->CreateWideAngleCamera("WideAngleCameraL16");
525+
ASSERT_NE(cameraL16, nullptr);
526+
cameraL16->SetImageFormat(PF_L16);
527+
528+
CameraLens lens;
529+
lens.SetCustomMappingFunction(1.05, 4.0, AFT_TAN, 1.0, 0.0);
530+
lens.SetType(MFT_CUSTOM);
531+
lens.SetCutOffAngle(GZ_PI);
532+
533+
cameraL8->SetLens(lens);
534+
cameraL8->SetHFOV(2.6);
535+
cameraL8->SetImageWidth(width);
536+
cameraL8->SetImageHeight(height);
537+
scene->RootVisual()->AddChild(cameraL8);
538+
539+
cameraL16->SetLens(lens);
540+
cameraL16->SetHFOV(2.6);
541+
cameraL16->SetImageWidth(width);
542+
cameraL16->SetImageHeight(height);
543+
scene->RootVisual()->AddChild(cameraL16);
544+
545+
// create blue material
546+
MaterialPtr blue = scene->CreateMaterial();
547+
blue->SetAmbient(0.0, 0.0, 0.3);
548+
blue->SetDiffuse(0.0, 0.0, 0.8);
549+
blue->SetSpecular(0.5, 0.5, 0.5);
550+
551+
// create box visual in front of cameras
552+
VisualPtr box = scene->CreateVisual();
553+
box->AddGeometry(scene->CreateBox());
554+
box->SetOrigin(0.0, 0.0, 0.0);
555+
box->SetLocalPosition(2, 0, 0);
556+
box->SetLocalScale(1, 1, 1);
557+
box->SetMaterial(blue);
558+
root->AddChild(box);
559+
560+
// Set a callback on the camera sensor to get a wide angle camera frame
561+
gz::common::ConnectionPtr connection =
562+
cameraL8->ConnectNewWideAngleFrame(
563+
std::bind(OnNewWideAngleFrameMono,
564+
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
565+
std::placeholders::_4, std::placeholders::_5));
566+
ASSERT_NE(nullptr, connection);
567+
gz::common::ConnectionPtr connection2 =
568+
cameraL16->ConnectNewWideAngleFrame(
569+
std::bind(OnNewWideAngleFrameMono,
570+
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
571+
std::placeholders::_4, std::placeholders::_5));
572+
ASSERT_NE(nullptr, connection2);
573+
574+
g_counter = 0;
575+
576+
// Update and verify
577+
cameraL8->Update();
578+
EXPECT_EQ(1, g_counter);
579+
580+
cameraL16->Update();
581+
EXPECT_EQ(2, g_counter);
582+
583+
// Verify image format
584+
EXPECT_EQ(PF_L8, cameraL8->ImageFormat());
585+
EXPECT_EQ(PF_L16, cameraL16->ImageFormat());
586+
587+
// verify cameras can see the box in the middle and the pixel color value
588+
// should be darker than the background (pixel at top center of image)
589+
const unsigned int step = width;
590+
unsigned int topMid = static_cast<unsigned int>(
591+
step / 2.0);
592+
unsigned int mid = static_cast<unsigned int>(
593+
height / 2.0 * step + step / 2.0);
594+
unsigned int topMidValue8 = g_bufferL8[topMid];
595+
uint16_t topMidValue16 = reinterpret_cast<uint16_t *>(g_bufferL16)[topMid];
596+
unsigned int midValue8 = g_bufferL8[mid];
597+
uint16_t midValue16 = reinterpret_cast<uint16_t *>(g_bufferL16)[mid];
598+
599+
EXPECT_GT(topMidValue8, midValue8);
600+
EXPECT_GT(topMidValue16, midValue16);
601+
602+
// Clean up
603+
delete [] g_bufferL8;
604+
g_bufferL8 = nullptr;
605+
delete [] g_bufferL16;
606+
g_bufferL16 = nullptr;
607+
engine->DestroyScene(scene);
608+
}

0 commit comments

Comments
 (0)