aldebaran/libqi

Trying to port NAOqi SDK example to libqi

Closed this issue · 9 comments

I want to port the following code found in the NAOqi Python SDK examples here:
http://doc.aldebaran.com/2-4/naoqi/vision/alphotocapture-tuto.html#alphotocapture-tuto

Please help me with the syntax.

The Python SDK code is:


#This test demonstrates how to use the ALPhotoCapture module.
#Note that you might not have this module depending on your distribution
import os
import sys
import time
from naoqi import ALProxy

#Replace this with your robot's IP address
IP = "10.0.252.91"
PORT = 9559

#Create a proxy to ALPhotoCapture
try:
photoCaptureProxy = ALProxy("ALPhotoCapture", IP, PORT)
except Exception, e:
print "Error when creating ALPhotoCapture proxy:"
print str(e)
exit(1)

#Take 3 pictures in VGA and store them in /home/nao/recordings/cameras/
photoCaptureProxy.setResolution(2)
photoCaptureProxy.setPictureFormat("jpg")
photoCaptureProxy.takePictures(3, "/home/nao/recordings/cameras/", "image")

#This call returns ['/home/nao/recordings/cameras/image_0.jpg', '/home/nao/recordings/cameras/image_1.jpg', '/home/nao/recordings/cameras/image_2.jpg']


My attempt is here:


#include <qi/applicationsession.hpp>
#include <qi/eventloop.hpp>

int main(int argc, char** argv)
{
qi::ApplicationSession app(argc, argv);
app.start();
qi::SessionPtr session = app.session();
qi::AnyObject photo_capture_service = session->service("ALPhotoCapture");
photo_capture_service.async("setResolution", 2);
photo_capture_service.async("setPictureFormat", "jpg");
photo_capture_service.async("takePictures", 3, "/home/nao/recordings/cameras/", "image");
}


Here are the errors I get:
main.cpp:16:48: error: no matching function for call to ‘qi::Objectqi::Empty::async(const char [14], int)’ photo_capture_service.async("setResolution", 2);
main.cpp:17:55: error: no matching function for call to ‘qi::Objectqi::Empty::async(const char [17], const char [4])’ photo_capture_service.async("setPictureFormat", "jpg");
main.cpp:18:89: error: no matching function for call to ‘qi::Objectqi::Empty::async(const char [13], int, const char [30], const char [6])’ photo_capture_service.async("takePictures", 3, "/home/nao/recordings/cameras/", "image");

Hi Johnny,

The code seems correct except the part with async. First you should use call and you all must define the return type. If you dont need to get data back you can just put void.

Look at the example here:
http://doc.aldebaran.com/libqi/guide/cxx-client.html#guide-cxx-client

so try something like:

photo_capture_service.call<void>("setResolution", 2);

Hi Cedric,

Thanks, your suggestion worked.

Now I'm trying to port another NAOqi SDK example to QI. Again, there are some diffulties with mapping the functions.

Here's the example:


#include <alproxies/alvideodeviceproxy.h>
#include <alvision/alvisiondefinitions.h>
#include < iostream>

int main(int argc, char **argv)
{
if (argc < 2) {
std::cerr << "Usage: videodevice_imageremote pIp" << std::endl;
return 1;
}
const std::string pIp = argv[1];

// Proxy to ALVideoDevice.
AL::ALVideoDeviceProxy cameraProxy(pIp);

// Subscribe a Vision Module to ALVideoDevice, starting the
// frame grabber if it was not started before.
std::string subscriberID = "subscriberID";
int fps = 5;
// The subscriberID can be altered if other instances are already running
subscriberID = cameraProxy.subscribe(subscriberID, AL::kVGA, AL::kRGBColorSpace, fps);

// Retrieve the current image.
AL::ALValue results;
results = cameraProxy.getImageRemote(subscriberID);
const unsigned char* imageData = static_cast<const unsigned char*>(results[6].GetBinary());

if (imageData == NULL) {
std::cerr << "Could not retrieve current image." << std::endl;
return 0;
}
else {
std::cout << "Image retrieved." << std::endl;
}

cameraProxy.releaseImage(subscriberID);

// Unsubscribe the V.M.
cameraProxy.unsubscribe(subscriberID);

return 1;
}


Here's my code:


#include < iostream>
#include <qi/applicationsession.hpp>
#include <qi/vision_definitions.h>
#include <qi/anyvalue.hpp>

using namespace std;

int main(int argc, char** argv)
{
qi::ApplicationSession app(argc, argv);
app.startSession();
qi::SessionPtr session = app.session();
qi::AnyObject video_service = session->service("ALVideoDevice");

/* Subscribe a client image requiring 320*240 and BGR colorspace.*/
video_service.call<void>("subscribe", "test", kQVGA, kBGRColorSpace, 30);

qi::AnyValue results;
results = video_service.call<qi::AnyValue>("getImageRemote","test");
std::cout << "populated results" << std::endl;
const unsigned char* imageData = static_cast<const unsigned char*>(results[6].GetBinary());
if (imageData == NULL) {
    std::cerr << "Could not retrieve current image." << std::endl;
    return 0;
  }   
  else {
    std::cout << "Image retrieved." << std::endl;
  }   

video_service.call<void>("releaseImage","test");
video_service.call<void>("unsubscribe","test");
    
return 1;

}


Here's the error:


error: ‘class qi::AnyReference’ has no member named ‘GetBinary’
const unsigned char* imageData = static_cast<const unsigned char*>(results[6].GetBinary());


I understand that GetBinary is a call from ALValue. ALValue doesn't exist in QI, so the closest thing I could find was qi::AnyValue.

What is the equivalent method in AnyValue to retrieving data from an AnyValue variable?

You should keep on using ALValue here. qi::AnyValue can wrap an ALValue, but cannot manage the access to the binary data ALValue is giving you. My suggestion:

auto results = video_service.call<AL::ALValue>("getImageRemote","test");
std::cout << "populated results" << std::endl;
const unsigned char* imageData = static_cast<const unsigned char*>(results[6].GetBinary());

Thank you for your prompt response. The reason I'm switching to QI as opposed to using the AL SDK is because I am compiling my code for the ARM architecture. As far as I understand, the AL SDK is not compiled for the ARM architecture, and this is why I am attempting to make remote calls to the AL library using QI.

Here is the error I get when I incorporate your suggestion.

error: ‘AL’ was not declared in this scope
auto results = video_service.callAL::ALValue("getImageRemote","test");
^

Hello, I did same things some times ago. I use a RaspberryPi to communicate with Nao. Working with qi::AnyValue or qi::AnyReference is hard because you don't know what is inside.
These functions may help you:

 // ******************************************************************
    // check if a qiValue is a basic data type: string, float or int
    // a basic data type is displayable directly
    static bool isQIBasicType(qi::AnyValue value)
    {
        return isQIBasicType(value.asReference());
    }

    // ******************************************************************
    // check if a qiValue is a basic data type: string, float or int
    // a basic data type is displayable directly
    static bool isQIBasicType(qi::AnyReference value)
    {
        if (value.kind() == qi::TypeKind_Int)
            return true;
        if (value.kind() == qi::TypeKind_Float)
            return true;
        if (value.kind() == qi::TypeKind_String)
            return true;
        return false;
    }

    // ******************************************************************
    static void displayQIType(qi::AnyValue value)
    {
        displayQIType(value.asReference());
    }

    // ******************************************************************
    static void displayQIType(qi::AnyReference value)
    {
        if (isQIBasicType(value)) {
            displayQIValue(value);
            return;
        }
        if (value.kind() == qi::TypeKind_Raw) {
            cout << "(raw)";
        } else if (value.kind() == qi::TypeKind_Pointer) {
            cout << "(pointer)";
        } else if (value.kind() == qi::TypeKind_Iterator) {
            cout << "(interator)";
        } else if (value.kind() == qi::TypeKind_List) {
            cout << "(list)";
        } else if (value.kind() == qi::TypeKind_Map) {
            cout << "(map)";
        } else if (value.kind() == qi::TypeKind_Tuple) {
            cout << "(tuple)";
        } else if (value.kind() == qi::TypeKind_Dynamic) {
            cout << "(dynamic)";
        } else if (value.kind() == qi::TypeKind_VarArgs) {
            cout << "(VarArgs)";
        } else if (value.kind() == qi::TypeKind_Object) {
            cout << "(object)";
        } else if (value.kind() == qi::TypeKind_Void) {
            cout << "(void)";
        } else {
            cout << "(unknown type)";
        }
    }

    // ******************************************************************
    static bool displayQIValue(qi::AnyValue value)
    {
        return displayQIValue(value.asReference());
    }

    // ******************************************************************
    static bool displayQIValue(qi::AnyReference value)
    {
        if (value.kind() == qi::TypeKind_Int) {
            cout << "Int=" << value.asInt32();
            return true;
        } else if (value.kind() == qi::TypeKind_Float) {
            cout << "Float=" << value.asFloat();
            return true;
        } else if (value.kind() == qi::TypeKind_String) {
            cout << "String=" << value.asString();
            return true;
        } else if (value.kind() == qi::TypeKind_List) {
            qi::AnyReferenceVector memData_anyref = value.asListValuePtr();
             cout << "LIST LOOP:";
            for (uint j = 0; j < memData_anyref.size(); j++) {
                displayQIValue(memData_anyref[j].content());
                //qi::AnyReference memref = memData_anyref[j].content();
            }
        } else {
            cout << "unknown value" << endl;
            return false;
        }
        return false;
    }

Indeed type erasure happens through the network, so you need to know what is inside to cast it properly. In this case, it is raw binary data we're looking for, so my best guess is to call result[6].asRaw(). I don't know if it works, but there must be a way, since libQi manages to serialize it.

As Victor said, it works one way or the other. Conversions are provided between ALValue and AnyValue.

Blind guess of the correct code:

auto results = video_service.call<std::vector<qi::AnyValue>>("getImageRemote","test");
std::pair<char*, size_t> buffer = results[6].asRaw();

Cedric, Zazics, thanks for your suggestions! I incorporated your advice, but still not getting results.
Here's the code and the results follow. The value that I'm getting back is type void. Not good.

The std::runtime_error happens at std::pair<char*, size_t> buffer = results2[6].asRaw();

This is how I call the executable:
./video_stream --qi-url=9.1.64.231


/*

  • Copyright (c) 2012-2015 Aldebaran Robotics. All rights reserved.
  • Use of this source code is governed by a BSD-style license that can be
  • found in the COPYING file.
    /
    /
    • Retrieve an image from the camera.
    • The image is returned in the form of a container object, with the
    • following fields:
    • 0 = width
    • 1 = height
    • 2 = number of layers
    • 3 = colors space index (see alvisiondefinitions.h)
    • 4 = time stamp (seconds)
    • 5 = time stamp (micro seconds)
    • 6 = image buffer (size of width * height * number of layers)
      */

#include < iostream>
#include <qi/applicationsession.hpp>
#include <qi/vision_definitions.h>
#include <qi/anyvalue.hpp>

using namespace std;

// ******************************************************************
static bool displayQIValue(qi::AnyReference value)
{
if (value.kind() == qi::TypeKind_Int) {
cout << "Int=" << value.asInt32();
return true;
} else if (value.kind() == qi::TypeKind_Float) {
cout << "Float=" << value.asFloat();
return true;
} else if (value.kind() == qi::TypeKind_String) {
cout << "String=" << value.asString();
return true;
} else if (value.kind() == qi::TypeKind_List) {
qi::AnyReferenceVector memData_anyref = value.asListValuePtr();
cout << "LIST LOOP:";
for (uint j = 0; j < memData_anyref.size(); j++) {
displayQIValue(memData_anyref[j].content());
//qi::AnyReference memref = memData_anyref[j].content();
}
} else {
cout << "unknown value" << endl;
return false;
}
return false;
}

// ******************************************************************
static bool displayQIValue(qi::AnyValue value)
{
return displayQIValue(value.asReference());
}

// ******************************************************************
// check if a qiValue is a basic data type: string, float or int
// a basic data type is displayable directly
static bool isQIBasicType(qi::AnyReference value)
{
if (value.kind() == qi::TypeKind_Int)
return true;
if (value.kind() == qi::TypeKind_Float)
return true;
if (value.kind() == qi::TypeKind_String)
return true;
return false;
}

// ******************************************************************
// check if a qiValue is a basic data type: string, float or int
// a basic data type is displayable directly
static bool isQIBasicType(qi::AnyValue value)
{
return isQIBasicType(value.asReference());
}

// ******************************************************************
static void displayQIType(qi::AnyReference value)
{
if (isQIBasicType(value)) {
displayQIValue(value);
return;
}
if (value.kind() == qi::TypeKind_Raw) {
cout << "(raw)" << endl;
} else if (value.kind() == qi::TypeKind_Pointer) {
cout << "(pointer)" << endl;
} else if (value.kind() == qi::TypeKind_Iterator) {
cout << "(interator)" << endl;
} else if (value.kind() == qi::TypeKind_List) {
cout << "(list)" << endl;
} else if (value.kind() == qi::TypeKind_Map) {
cout << "(map)" << endl;
} else if (value.kind() == qi::TypeKind_Tuple) {
cout << "(tuple)" << endl;
} else if (value.kind() == qi::TypeKind_Dynamic) {
cout << "(dynamic)" << endl;
} else if (value.kind() == qi::TypeKind_VarArgs) {
cout << "(VarArgs)" << endl;
} else if (value.kind() == qi::TypeKind_Object) {
cout << "(object)" << endl;
} else if (value.kind() == qi::TypeKind_Void) {
cout << "(void)" << endl;
} else {
cout << "(unknown type)" << endl;
}
}

// ******************************************************************
static void displayQIType(qi::AnyValue value)
{
displayQIType(value.asReference());
}

// ******************************************************************

int main(int argc, char** argv)
{
qi::ApplicationSession app(argc, argv);
app.startSession();
qi::SessionPtr session = app.session();
qi::AnyObject video_service = session->service("ALVideoDevice");

video_service.call<void>("subscribe", "test", kQVGA, kBGRColorSpace, 30);
qi::AnyValue results = video_service.call<qi::AnyValue>("getImageRemote","test");
std::cout << "results displayQIType: " << std::endl;
displayQIType(results);

auto results2 = video_service.call<std::vector<qi::AnyValue>>("getImageRemote","test");
std::pair<char*, size_t> buffer = results2[6].asRaw();

return 1;

}


results:


results displayQIType:
(void)
terminate called after throwing an instance of 'std::runtime_error'
what(): Return argument conversion error: Unable to convert call result to target type: from Void to List
Aborted (core dumped)


ALVideoDevice API limits the number of subscribers with the same name to 6. After that, you receive null images. This is a common pitfall of this API and is not due to libqi. To fix it, make sure you unsubscribe all your "leaked" subscribers before getting the image again :

pepper [0] ~ $ qicli call ALVideoDevice.getSubscribers
["ALBasicAwareness_0","FrameGetter/Camera_0","FrameGetter/Camera_1","FrameGetter/Depth_0","FrameGetter/Depth_1","VideoDevice_DepthCamera<QQVGA>_0","VideoDevice_DistanceCamera<QQVGA>_0","VideoDevice_TopCamera<VGA>_0","VisionNavigation_0","test_0","test_1","test_2","test_3","test_4","test_5","test_6"]
pepper [0] ~ $ qicli call ALVideoDevice.unsubscribe test_0
true
...

Then you will see that you actually receive a vector of data. The following code works, for example:

  /* Subscribe a client image requiring 320*240 and BGR colorspace.*/
  auto subscriberId = video_service.call<std::string>("subscribe", "test", kQVGA, kBGRColorSpace, 30);

  auto results = video_service.call<qi::AnyValue>("getImageRemote", subscriberId);
  std::cout << "image " << qi::encodeJSON(results) << std::endl;
  auto imageAsValue = results[6];
  auto buffer = imageAsValue.content().asRaw();
  std::cout << "buffer size: " << buffer.second << std::endl;

  video_service.call<void>("releaseImage", subscriberId);
  video_service.call<void>("unsubscribe", subscriberId);

The call to content() does the trick of unwrapping the value out from the ALValue. Please also note the use of qi::encodeJSON that you can find in the include <qi/jsoncodec.hpp> to get a string representation of a qi::AnyValue.

Next time, please use Stack Overflow for dev support, this is not a libqi issue.