This Android library will help you:
- Search and detect Aruco markers with Pepper
- Make Pepper reach the marker locations, align with them
- 1. Video demonstration
- 2. Version History
- 3. Aruco markers
- 4. Getting started
- 5. Using the library in your project
- 6. Usage
- 6.1. Prerequisite: load OpenCV in our Activity
- 6.2.
DetectArucoMarker
action - 6.3.
LookAroundAndDetectArucoMarker
action- 6.3.1. Goal
- 6.3.2. Typical usage
- 6.3.3. Example
- 6.3.4. How to use it
- 6.3.5. Tunning the
LookAroundAndDetectArucoMarker
behavior- 6.3.5.1. Aruco markers detection parameters
- 6.3.5.2. LookAt movement Policy
- 6.3.5.3. Specifying LookAt targets as a list of Frames
- 6.3.5.4. Specifying LookAt targets as a list of Transforms
- 6.3.5.5. Specifying LookAt targets as a list of Spherical Coordinates
- 6.3.5.6. Termination callback
- 6.3.5.7. Aruco marker validation callback
- 6.3.5.8. Aruco marker detected listener
- 6.4.
GoToMarkerAndCheckItsPositionOnTheWay
action - 6.5.
ArucoMarker
object
- 7. Advanced usage
- 8. License
This video was filmed at SoftBank Robotics Europe, and shows Pepper detecting and navigating toward aruco marker placed in the SBRE Showroom.
Note: This 2.0 version is not backward compatible with previous releases, as there have been major changes, see the changelog for details
An Aruco marker is a synthetic square marker composed by a wide black border and an inner binary matrix. The black border facilitates its fast detection in the image and the binary codification allows its identification and the application of error detection and correction techniques. The markers can be of different size, 4x4, 5x5 or 6x6.
Some examples of ArUco markers:
A dictionary of markers is the set of markers that are considered in a specific application. Each marker has an id. This id is the marker index within the dictionary it belongs to.
The markers have an orientation. In this library, we define the Aruco axis as follow:
We chose this coordinate system to be consistent with Pepper QiSDK coordinate system. Beware however as OpenCV uses another coordinate system on material you'll find out there on the internet.
The project comes complete with a sample project. You can clone the repository, open it in Android Studio, and run this directly onto a Robot.
The sample application contains a tutorial that will demonstrate what you can do with the library. You will need to have Aruco Markers in order to play with the tutorial. See Print Aruco markers
You can generate Aruco Markers, for instance with this website. Choose 4x4 aruco markers, of size 150mm, and pick an ID below 50.
If you want you can also directly print the following markers (click on each image to open them):
Make sure to replace 'Tag' by the number of the version of the library you want to use, or by 'master-SNAPSHOT' to use the master branch.
You have two options to add the OpenCV libraries to your project:
With this method you directly add the libraries to your apk. The disadvantage of this method is that your apk will become very big.
Copy the .so files you will find in the folder pepperaruco/src/main/jniLibs into your own src/main folder. Android studio will automatically find and include these libraries into your apk.
With this method, the opencv libraries are not installed with your apk, you need to install them separately on the robot.
To install OpenCV manager APK, connect to your robot ip (for instance 10.0.204.180) with adb and install the package you will find in the folder opencv-apk:
$ adb connect 10.0.204.180:5555
$ adb install opencv-apk/OpenCV_3.4.7-dev_Manager_3.47_armeabi-v7a.apk
In your Activity class, you need to load opencv in the onCreate
method in order to be able to use the library. To do so, call OpenCVUtils.loadOpenCV(this)
:
class MyActivity : AppCompatActivity(), RobotLifecycleCallbacks {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
OpenCVUtils.loadOpenCV(this)
//...
}
Detect an Aruco marker with Pepper Top Camera.
Use it whenever you want to detect if there are Aruco markers in Pepper vision field.
You have to turn Pepper's head yourself toward the Aruco marker using a LookAt
or an ExtraLookAt
. If you don't want to handle Pepper's head orientation, use LookAroundAndDetectArucoMarker
.
The following example will detect Aruco markers in Pepper's field of view, and display the number of markers detected, as well as the id of each detected marker.
// Create the action
val detectAruco: DetectArucoMarker = DetectArucoMarkerBuilder.with(qiContext)
.withMarkerLength(0.15)
.withDictionary(ArucoDictionary.DICT_4X4_100)
.withArucoMarkerFrameLocalizationPolicy(ArucoMarkerFrameLocalizationPolicy.DETACHED)
.build()
// Run it
val arucoMarkers: Set<ArucoMarker> = detectAruco.run()
// Display the result
Log.i(TAG, "Pepper detected ${arucoMarkers.size} marker(s)")
arucoMarkers.forEach { marker ->
Log.i(TAG, "- marker $marker.id")
}
The DetectArucoMarker
action returns a set of ArucoMarker
s objects corresponding to the markers that were detected by Pepper using its Top Camera.
Camera shake will prevent correct detection of Aruco markers. Make sure Pepper head stays still while you use the DetectArucoMarker
action. We advise that you hold BasicAwareness
as well as BackgroundMovement
while you run the DetectArucoMarker
action.
fun DetectArucoMarkerBuilder.withMarkerLength(markerLength: Double): DetectArucoMarkerBuilder
Set the size of the side of the Aruco markers. In meters.
By default, DetectArucoMarker
uses 0.15 meters.
fun DetectArucoMarkerBuilder.withDictionary(dictionary: ArucoDictionary): DetectArucoMarkerBuilder
A dictionary of markers is the set of markers that are considered in a specific application. The main properties of a dictionary are the dictionary size and the marker size.
- The dictionary size is the number of markers that compose the dictionary.
- The marker size is the size of those markers (the number of bits).
By default, DetectArucoMarker
use ArucoDictionary.DICT_4X4_50
.
fun DetectArucoMarkerBuilder.withCameraMatrix(cameraMatrix: HashMap<Pair<Int, Int>, DoubleArray>): DetectArucoMarkerBuilder
You normally should not have to set this parameter. It's the calibration parameter of the Top Camera. It allows you to set the camera matrix parameter used by OpenCV.
By default, DetectArucoMarker
uses the matrix defined in PepperRobotData.HEAD_CAMERA_MATRIX
.
fun DetectArucoMarkerBuilder.withDistortionCoefs(distortionCoefs: DoubleArray): DetectArucoMarkerBuilder
You normally should not have to set this parameter. It's the calibration parameter of the Top Camera. It allows you to set the distortion coefficient parameter used by OpenCV.
By default, DetectArucoMarker
uses the coefficients defined in PepperRobotData.HEAD_CAMERA_DISTORTION_COEFS
.
fun DetectArucoMarkerBuilder.withArucoMarkerFrameLocalizationPolicy(policy: ArucoMarkerFrameLocalizationPolicy): DetectArucoMarkerBuilder
When Aruco markers are detected, the Frame
that represent their position can be:
ArucoMarkerFrameLocalizationPolicy.DETACHED
: This is the Default. ArucoFrame
s won't be attached to any frame. With time their positions will be less and less accurate because of robot drift. Only use this when the robot DOES NOT move. If robot moves, then useLocalize
action and chooseATTACHED_TO_MAPFRAME
ArucoMarkerFrameLocalizationPolicy.ATTACHED_TO_MAPFRAME
: ChooseATTACHED_TO_MAPFRAME
whenLocalize
orLocalizeAndMap
is running. ArucoFrame
s will be attached tomapFrame
, and their positions will be automatically corrected and stay accurate.
Look around Pepper to search for Aruco markers on the floor, on the wall.
Use it whenever you want to detect the Aruco markers that are around Pepper. This action handles the moving of Pepper and the detection of markers for you.
The following example will detect Aruco markers on the floor in front of Pepper, and display the number of markers detected, as well as the id of each marker detected.
// Create the action
val lookAroundAndDetect: LookAroundAndDetectArucoMarker =
LookAroundAndDetectArucoMarkerBuilder.with(qiContext)
.withMarkerLength(0.15)
.withDictionary(ArucoDictionary.DICT_4X4_100)
.withArucoMarkerFrameLocalizationPolicy(ArucoMarkerFrameLocalizationPolicy.DETACHED)
.withLookAtMovementPolicy(LookAtMovementPolicy.HEAD_AND_BASE)
.withLookAtSphericalCoordinates(120.0 to 0.0, 120.0 to 20.0, 120.0 to -20.0)
.build()
// Run it
val arucoMarkers: Set<ArucoMarker> = lookAroundAndDetect.run()
// Display the result
Log.i(TAG, "Pepper detected ${arucoMarkers.size} marker(s)")
arucoMarkers.forEach { marker ->
Log.i(TAG, "- marker $marker.id")
}
The LookAroundAndDetectArucoMarker
action returns a set of ArucoMarker
s objects corresponding to the markers that were detected by Pepper.
This action relies on DetectArucoMarker
under the hood. So you can set the same parameters as in DetectArucoMarker
:
- the marker size
fun LookAroundAndDetectArucoMarkerBuilder.withMarkerLength(markerLength: Double): LookAroundAndDetectArucoMarkerBuilder
- the aruco dictionary
fun LookAroundAndDetectArucoMarkerBuilder.withDictionary(dictionary: ArucoDictionary): LookAroundAndDetectArucoMarkerBuilder {
- the camera matrix
fun LookAroundAndDetectArucoMarkerBuilder.withCameraMatrix(cameraMatrix: HashMap<Pair<Int, Int>, DoubleArray>): LookAroundAndDetectArucoMarkerBuilder
fun LookAroundAndDetectArucoMarkerBuilder.withDistortionCoefs(distortionCoefs: DoubleArray): LookAroundAndDetectArucoMarkerBuilder
fun LookAroundAndDetectArucoMarkerBuilder.withArucoMarkerFrameLocalizationPolicy(policy: ArucoMarkerFrameLocalizationPolicy): LookAroundAndDetectArucoMarkerBuilder
fun LookAroundAndDetectArucoMarkerBuilder.withLookAtMovementPolicy(policy: LookAtMovementPolicy): LookAroundAndDetectArucoMarkerBuilder
This action relies on LookAt
under the hood. When Pepper looks at locations in search for Aruco markers, you can choose if it should use only his head or also move his mobile base by setting the LookAtMovementPolicy
.
By default, the LookAtMovementPolicy
is set to HEAD_AND_BASE
, but it can be set to HEAD_ONLY
.
If you choose HEAD_ONLY
, Pepper will only turn his head to look around. However, if you use HEAD_AND_BASE
, Pepper will be able to also turn his base.
fun LookAroundAndDetectArucoMarkerBuilder.withLookAtTargetFrame(vararg frame: Frame): LookAroundAndDetectArucoMarkerBuilder
LookAroundAndDetectArucoMarker
requires that you specify a list of target locations where Pepper will look to search for Aruco markers.
You can set the target location as a list of Frame
s. Pepper will look at the Frame
s one after another.
fun LookAroundAndDetectArucoMarkerBuilder.withLookAtReferenceFrameAndTransforms(lookAtRefFrame: Frame, lookAtTransforms: List<Transform>): LookAroundAndDetectArucoMarkerBuilder
LookAroundAndDetectArucoMarker
requires that you specify a list of target locations where Pepper will look to search for Aruco markers.
You can set the target locations as a reference Frame
and a list of Transforms
. Pepper will compute target frames using the reference Frame
combined with the transforms, and look at those target Frame
s one after another.
The reference Frame
usually should not move. Especially if you choose gazeFrame
as the reference frame, do not pass gazeFrame
but a copy of it (use qiContext.mapping.makeDetachedFrame(gazeFrame)
)
fun LookAroundAndDetectArucoMarkerBuilder.withLookAtSphericalCoordinates(vararg sphericalCoordinates: Pair<Double, Double>): LookAroundAndDetectArucoMarkerBuilder
LookAroundAndDetectArucoMarker
requires that you specify a list of target locations where Pepper will look to search for Aruco markers.
You can set the target location as a list of pairs of spherical coordinates (theta, phi).
Pepper will compute Frame
s using these coordinates and look at them one after another.
fun LookAroundAndDetectArucoMarkerBuilder.withTerminationCallback(terminationCallback: LookAroundAndDetectArucoMarker.TerminationCallback): LookAroundAndDetectArucoMarkerBuilder
By default the LookAroundAndDetectArucoMarker
will look at all the target locations you specified, detect if there are Aruco markers at these locations. Then return.
Sometimes you may want to search for a particular Aruco marker, and return immediately once this marker is found. Or you may want to return immediately once any marker is found.
To address these use cases you can set the termination callback. You have to provide a subclass of the interface TerminationCallback
, with an implementation of the shouldTerminate
function.
interface TerminationCallback {
fun shouldTerminate(currentlyDetectedMarkers: Set<ArucoMarker>): Future<Boolean>
}
The shouldTerminate
function receive a set containing the Aruco marker detected up to now, and must return a Future
equal to true
when it is satisfied with the current set of Aruco markers detected and want the action to terminate.
We provide several ready made implementations of TerminationCallback
:
TerminateOnlyWhenLookAroundHasFinished()
: this is the default, it terminates only after Pepper has looked at all locationsTerminateWhenSomeMarkersDetected
: terminate whenever a marker is detected, or if none has been detected and Pepper has looked at all locations.TerminateWhenSpecificMarkerDetected(val markerId: Int)
: terminate when the marker with idmarkerId
has been detected, or if it has not been detected and Pepper has looked at all locations.
fun LookAroundAndDetectArucoMarkerBuilder.withArucoMarkerValidationCallback(callback: LookAroundAndDetectArucoMarker.ArucoMarkerValidationCallback): LookAroundAndDetectArucoMarkerBuilder
Discard some of the markers detected by setting the marker validation callback. By default all markers are valid, but you can provide a subclass of ArucoMarkerValidationCallback
, with an implementation of the isMarkerValid
function.
interface ArucoMarkerValidationCallback {
fun isMarkerValid(marker: ArucoMarker): Future<Boolean>
}
The isMarkerValid
function takes an ArucoMarker
s as parameter, and should return a Future
equal to true
if it's valid, false
otherwise.
You can combine marker validation with termination callback:
Let say that there are markers around Pepper on the wall and on the floor, and you want to terminate whenever you see any marker on the floor.
- You could define a validation callback that reject the markers that are not on the floor
- And use
TerminateWhenSomeMarkersDetected
as a termination callback
interface OnArucoMarkerDetectedListener {
fun onArucoMarkerDetected(markers: Set<ArucoMarker>)
}
fun addOnArucoMarkerDetectedListener(listener: OnArucoMarkerDetectedListener)
fun removeOnArucoMarkerDetectedListener(listener: OnArucoMarkerDetectedListener)
fun removeAllOnArucoMarkerDetectedListeners()
Observe markers detection by setting the marker detected listener. The listener will be called whenever Pepper detects Aruco markers. The listener has to be a subclass of OnArucoMarkerDetectedListener
and overload the onArucoMarkerDetected
function.
Go to the marker Frame
s, and on the way to the marker regularly stop and redetect the marker to verify its position.
Use it when you have an Aruco marker on the floor, and you want Pepper to go on top of it, and you are not sure the position of the marker was detected precisely enough (for instance when detecting a marker from far away, their position might not be very precise).
// Get a previously detected aruco marker
val marker: ArucoMarker = ...
// Create the action
val goToMarker: GoToMarkerAndCheckItsPositionOnTheWay =
GoToMarkerAndCheckItsPositionOnTheWayBuilder.with(qiContext)
.withMarker(marker)
.build()
// Run it
val (positionReachedWithSuccess, detectedMarker) = goToMarker.run()
// Display the outcome of the action
if (positionReachedWithSuccess)
Log.i(TAG, "Marker position was reached")
else
Log.i(TAG, "Marker position was not reached")
if (detectedMarker != null)
Log.i(TAG, "Marker was detected on the way")
else
Log.i(TAG, "Marker was not detected on the way")
Use the withMarker
function of the GoToMarkerAndCheckItsPositionOnTheWayBuilder
builder to set the Aruco marker that Pepper will try to reach.
fun GoToMarkerAndCheckItsPositionOnTheWayBuilder.withMarker(marker: ArucoMarker): GoToMarkerAndCheckItsPositionOnTheWayBuilder
Enable the marker alignment if you want Pepper to orientate its body X axis with the marker Z axis.
If you want Pepper to align in another direction, you can use the markerZAxisRotation
parameter (in radian) to change it.
For instance, set it to 0.785
(pi / 4
) to align at 45 degrees from the Z axis.
fun GoToMarkerAndCheckItsPositionOnTheWayBuilder.withMarkerAlignmentEnabled(enabled: Boolean, markerZAxisRotation: Double = 0.0): GoToMarkerAndCheckItsPositionOnTheWayBuilder
Enable the walking animation if you want Pepper to balance its arm while reaching the marker.
fun GoToMarkerAndCheckItsPositionOnTheWayBuilder.withWalkingAnimationEnabled(enabled: Boolean): GoToMarkerAndCheckItsPositionOnTheWayBuilder
One can specify a maximum navigating speed in m/s. There is no guarantee that the robot will reach that speed, only that it will not go faster than the specified maximum speed. By default, the robot will not go faster than 0.35 m/s.
fun GoToMarkerAndCheckItsPositionOnTheWayBuilder.withMaxSpeed(maxSpeed: Float): GoToMarkerAndCheckItsPositionOnTheWayBuilder
While going to the marker, Pepper will regularly stop to detect it and check its position. You can modify the maximum distance Pepper will travel before trying to redetect the marker.
fun GoToMarkerAndCheckItsPositionOnTheWayBuilder.withDistanceBetweenStops(distanceBetweenStops: Double): GoToMarkerAndCheckItsPositionOnTheWayBuilder
An ArucoMarker
object contains all the information about the Aruco marker that was detected, and especially its id and its position in space.
Aruco markers objects contain the following fields:
marker.id: Int
: the id (a positive number) that uniquely identifies the marker.marker.frame: Frame
: aFrame
, corresponding to the position of the marker in space.marker.detectionData.image: Bitmap
: the top camera image in which the marker was detectedmarker.detectionData.timestamp: Long
: the timestamp (QiSDK Timestamp) when the marker was detected
You can serialize an ArucoMarker
using the serialize
function. It will return a String
that you can save to a file and store persistently.
fun ArucoMarker.serialize(qiContext: QiContext): String
NB 1: Pepper MUST be localized (use a LocalizeAndMap
or a Localize
action) before you can serialize an ArucoMarker
from its content.
NB 2: Only ArucoMarker
with ATTACHED_TO_MAPFRAME
policy can be serialized.
To load an ArucoMarker
from its serialized content, you can use an ArucoMarkerBuilder
object.
val arucoData: String = ...
val arucoMarker = ArucoMarkerBuilder.with(qiContext)
.withMarkerString(arucoData)
.build()
NB: Pepper MUST be localized (use a LocalizeAndMap
or a Localize
action) before you can load an ArucoMarker
from its content.
Checking Aruco markers position and orientation can be useful to validate the marker you detect. For instance if you are searching markers on the floor, you don't want to detect markers on the wall. We provide several functions to help you validate detected Aruco markers positions.
- To know if the Aruco is horizontal or vertical, use:
fun Actuation.computeFrameOrientation(frame: Frame): FrameOrientation
You have to give the Aruco marker Frame
as parameter, and it will return a FrameOrientation
equal to FrameOrientation.HORIZONTAL
or FrameOrientation.VERTICAL
.
- To know precisely the orientation of the Aruco marker, use
fun Actuation.computeRobotRelativeFrameOrientation(frame: Frame): RobotRelativeFrameOrientation
You have to give the Aruco marker Frame
as parameter, and it will return a RobotRelativeFrameOrientation
:
data class RobotRelativeFrameOrientation(
val xAxisDirection: RobotRelativeDirection,
val yAxisDirection: RobotRelativeDirection,
val zAxisDirection: RobotRelativeDirection
)
Containing the direction of each axis relatively to Pepper, as a RobotRelativeDirection
:
enum class RobotRelativeDirection {
UP,
DOWN,
TOWARD_ROBOT,
RIGHT_OF_ROBOT,
LEFT_OF_ROBOT,
AWAY_FROM_ROBOT
}
To check a marker is on the floor, use isMarkerOnTheFloor
, and give the marker Frame
as parameter.
fun Actuation.isMarkerOnTheFloor(markerFrame: Frame): Boolean
It is possible to replace the version of opencv contained in this project. Though the complete method on how to do it exactly is left out of this README. One thing you need to make sure is you use a version of OpenCV compiled with Aruco Detection code in it (which is not the default). This version is called opencv contrib. You can find compiled versions here:
https://pullrequest.opencv.org/buildbot/builders/3_4-contrib_pack-contrib-android
Click on a build number, and then click on the upload release to find the archive called OpenCV4Android.zip You will find the files you need in this archive.
The full opencv aruco documentation can be found here:
https://docs.google.com/document/d/1QU9KoBtjSM2kF6ITOjQ76xqL7H0TEtXriJX5kwi9Kgc/edit#
This project is licensed under the BSD 3-Clause "New" or "Revised" License- see the COPYING file for details