ROS与javascript入门教程-roslibjs-发布视频和IMU消息
ROS与javascript入门教程-roslibjs-发布视频和IMU消息
说明:
- 介绍通过roslibjs和rosbridge使用手机发布视频和IMU消息
实现:
- 建立camera.html文件,放置服务器端并可以远程访问,当在手机运行camera.html
- 可通过rviz订阅 "image" or "imu"话题。
- 需要浏览器支持DeviceOrientationEvent class 或 the getUserMedia() method.
- 可用Android的chrome浏览器进行测试
步骤:
- 新建 camera.html文件
- 代码如下:
<!DOCTYPE html>
<html>
<head>
<script src="http://cdn.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js"></script>
<script src="http://cdn.robotwebtools.org/roslibjs/current/roslib.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>
<script>
// these two lines contain the base64-encoded images to turn on- and off the application.
var RECORD_ON ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAASFBMVEUAAAAAAADMzMz/AABmZmaZmZnMAABmAACZAAAzAAAzMzP/MzPMZmaZMzPMMzP/MwD/////Zmb/ZgBmMzPMmZmZZmb/mQD/mZlxvxKlAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffCBIMMyVTqZicAAACK0lEQVRIx43W63aDIAwA4KJJhVjrVtfu/d90kASIFlzzp5fDd5KAgJdLJwa8fB6YYojBXz4EQw1EgNPxAGDHqzpDBqDV0EN5DDoToIhagkSAOwb2DIEBRLEWINJf2DQspCQ7AwiUy0M69pEFyCR/xZCl4cTE/CgQtYoU2xZC2J6YiXNHA3ai4uApRwjaj+RBs+JWpKHLcrstiyAz3WjFUFJMk4/jf39+fqPyCdXJLmmwzi4L7+8a8avPhlwtzSQRcY0x3+9z+mSz8bSY0jRJWoEirtdxlM+IluczEXA7In+FbVm8jJ9jqIpmC2nBSjc6XXFZwvS83XwC4/wdI5pRDLczUE6TSJoPisRrDhNCkqFSmTY/pN6ZpIo05pguGZk1qawSKL3PxnznNIGHkDSjK1/q4tZNnlFI0CdNCeiTooRjXVfTTV5PJnggWTxejzNCDfJ6PdYWGd6zXHOWR7ewXi/jOrYJ8PbCvFH8cSGFlBmjQoZC/Dup68LbjAmWR39fWaOu1MRFCNg0rWesrCQTU1njuaxPZW1lX1neYRXwvgy1rkLQGN2WZVOWJHFPDZBPMUTXMVZAvZ+gnolqfBlvT6VEymGJelzryedL7M4+MFdTvIwAnDn8JnPCupbgO7KaeijHLz1xNKzseBVweTPketEQQgDagLAhssFWIl6P5j3OpK5QzgB8lXVufiFoFkl/n71fZIPlDeYsxa64+l7yH1BFUAv6ANgZ7wz/A295IO9de7kCAAAAAElFTkSuQmCC";
var RECORD_OFF = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAFVBMVEVvcm0AAAAzMzNmZmbMzMyZmZn///8pcdebAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffCBINBR5qfIr6AAABv0lEQVQ4y22UQZaCMAyGffo8QED3Q9E9EDmAYzrrgZIbzJv7H2GStFLLmA22H/+fWNLsdi/hd2+DeSHP/A6Qhg9duX/AhVJskAGPoyLsNmACjVrQkMFe3u0hxUKPj5W0BqoWh0YeSxbtb2o1xvyNGPqnCMkLYPr6/SEOABcKqeKbF8mV2UE1KgHysbyDStaQTCKKR8GpYHDoIJVuJ3hcyJZVK1ZanFbuv5XQIypaRBycoupkdkx3kzhBg3NOMl2j3UJ9LS+6NRCufSLyq9KtqBFVPWmi4+KBoUbZkQoUiMHniZTcYYYqmylpjUgBdyVohu2s5HxOpDdiSWbNA3UlZR/lBBqw/CYyIkenRL9ZqjfVFgln0vJckiaTkIkvNYXba57WZRLku03xPHPo2sj9DamVLFuiFZ2pE2IfbkNOQnY32hBdXrR7RoLSzhrhYWQqRNYj/NB281MhsrZiVGJtuYpiw7E16RiajGIrYrxdGHooo0ZMdxQ3BJ8XErHfSrrnxcamlOB65Qu/K+bbLWTOXrxKDKU/JU3I/DoQDsjMgwmYyyFyGNkGj4ykgOXgET8bSf+AqAZ1DIhvBhwivu7/ATYxlvOTH50RAAAAAElFTkSuQmCC";
var alpha, valpha, z;
var beta, vbeta, x;
var gamma, vgamma, y;
var cameraTimer, imuTimer;
// setup event handler to capture the orientation event and store the most recent data in a variable
if (window.DeviceOrientationEvent) {
// Listen for the deviceorientation event and handle the raw data
window.addEventListener('deviceorientation', function(eventData) {
// gamma is the left-to-right tilt in degrees, where right is positive
gamma = eventData.gamma;
// beta is the front-to-back tilt in degrees, where front is positive
beta = eventData.beta;
// alpha is the compass direction the device is facing in degrees
alpha = eventData.alpha
}, false);
};
// setup event handler to capture the acceleration event and store the most recent data in a variable
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', deviceMotionHandler, false);
} else {
window.alert("acceleration measurements Not supported.");
}
function deviceMotionHandler(eventData) {
// Grab the acceleration from the results
var acceleration = eventData.acceleration;
x = acceleration.x;
y = acceleration.y;
z = acceleration.z;
// Grab the rotation rate from the results
var rotation = eventData.rotationRate;
vgamma = rotation.gamma;
vbeta = rotation.beta;
valpha = rotation.alpha;
}
// setup connection to the ROS server and prepare the topic
var ros = new ROSLIB.Ros();
ros.on('connection', function() { console.log('Connected to websocket server.');});
ros.on('error', function(error) { console.log('Error connecting to websocket server: ', error); window.alert('Error connecting to websocket server'); });
ros.on('close', function() { console.log('Connection to websocket server closed.');});
var imageTopic = new ROSLIB.Topic({
ros : ros,
name : '/camera/image/compressed',
messageType : 'sensor_msgs/CompressedImage'
});
var imuTopic = new ROSLIB.Topic({
ros : ros,
name : '/gyro',
messageType : 'sensor_msgs/Imu'
});
</script>
</head>
<!-- declare interface and the canvases that will display the video and the still shots -->
<body>
<video style="display: none" autoplay id="video"></video>
<canvas style="display: none" id="canvas"></canvas>
<button id="startstop" style="outline-width: 0; background-color: transparent; border: none"><img id="startstopicon" src=""/></button>
<script>
document.getElementById('startstopicon').setAttribute('src', RECORD_OFF);
// request access to the video camera and start the video stream
var hasRunOnce = false,
video = document.querySelector('#video'),
canvas = document.querySelector('#canvas'),
width = 640,
height, // calculated once video stream size is known
cameraStream;
function cameraOn() {
navigator.getMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
navigator.getMedia(
{
video: true,
audio: false
},
function(stream) {
cameraStream = stream;
if (navigator.mozGetUserMedia) {
video.mozSrcObject = stream;
} else {
var vendorURL = window.URL || window.webkitURL;
video.src = vendorURL.createObjectURL(stream);
}
video.play();
},
function(err) {
console.log("An error occured! " + err);
window.alert("An error occured! " + err);
}
);
}
function cameraOff() {
cameraStream.stop();
hasRunOnce = false;
takepicture(); // blank the screen to prevent last image from staying
}
// function that is run once scale the height of the video stream to match the configured target width
video.addEventListener('canplay', function(ev){
if (!hasRunOnce) {
height = video.videoHeight / (video.videoWidth/width);
video.setAttribute('width', width);
video.setAttribute('height', height);
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
hasRunOnce = true;
}
}, false);
// function that is run by trigger several times a second
// takes snapshot of video to canvas, encodes the images as base 64 and sends it to the ROS topic
function takepicture() {
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
var data = canvas.toDataURL('image/jpeg');
var imageMessage = new ROSLIB.Message({
format : "jpeg",
data : data.replace("data:image/jpeg;base64,", "")
});
imageTopic.publish(imageMessage);
}
function imusnapshot() {
var beta_radian = ((beta + 360) / 360 * 2 * Math.PI) % (2 * Math.PI);
var gamma_radian = ((gamma + 360) / 360 * 2 * Math.PI) % (2 * Math.PI);
var alpha_radian = ((alpha + 360) / 360 * 2 * Math.PI) % (2 * Math.PI);
var eurlerpose = new THREE.Euler(beta_radian, gamma_radian, alpha_radian);
var quaternionpose = new THREE.Quaternion;
quaternionpose.setFromEuler(eurlerpose);
var imuMessage = new ROSLIB.Message({
header : {
frame_id : "world"
},
orientation : {
x : quaternionpose.x,
y : quaternionpose.y,
z : quaternionpose.z,
w : quaternionpose.w
},
orientation_covariance : [0,0,0,0,0,0,0,0,0],
angular_velocity : {
x : vbeta,
y : vgamma,
z : valpha,
},
angular_velocity_covariance : [0,0,0,0,0,0,0,0,0],
linear_acceleration : {
x : x,
y : y,
z : z,
},
linear_acceleration_covariance : [0,0,0,0,0,0,0,0,0],
});
imuTopic.publish(imuMessage);
}
// turning on and off the timer that triggers sending pictures and imu information several times a second
startstopicon.addEventListener('click', function(ev){
if(cameraTimer == null) {
ros.connect("ws://" + window.location.hostname + ":9090");
cameraOn();
cameraTimer = setInterval(function(){
takepicture();
}, 250); // publish an image 4 times per second
imuTimer = setInterval(function(){
imusnapshot();
}, 100); // publish an IMU message 10 times per second
document.getElementById('startstopicon').setAttribute('src', RECORD_ON);
} else {
ros.close();
cameraOff();
clearInterval(cameraTimer);
clearInterval(imuTimer);
document.getElementById('startstopicon').setAttribute('src', RECORD_OFF);
cameraTimer = null;
imuTimer = null;
}
}, false);
</script>
</body>
</html>
代码解析:
- 代码段:
var imageTopic = new ROSLIB.Topic({
ros : ros,
name : '/camera/image/compressed',
messageType : 'sensor_msgs/CompressedImage'
});
var imuTopic = new ROSLIB.Topic({
ros : ros,
name : '/gyro',
messageType : 'sensor_msgs/Imu'
- 解释:
- 创建图片话题对象和imu话题对象
- 代码段:
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
var data = canvas.toDataURL('image/jpeg');
var imageMessage = new ROSLIB.Message({
format : "jpeg",
data : data.replace("data:image/jpeg;base64,", "")
解释:
- 获取图片并转化成加密的字符串
代码段:
var imuMessage = new ROSLIB.Message({
header : {
frame_id : "world"
},
解释:
- 构造imu的消息
代码段:
startstopicon.addEventListener('click', function(ev){
if(cameraTimer == null) {
ros.connect("ws://" + window.location.hostname + ":9090");
cameraOn();
cameraTimer = setInterval(function(){
takepicture();
}, 250); // publish an image 4 times per second
imuTimer = setInterval(function(){
imusnapshot();
}, 100); // publish an IMU message 10 times per second
- 解释:
- 设置发布频率
运行:
- 服务器,新终端,运行
roslaunch rosbridge_server rosbridge_websocket.launch
- 安卓手机端,chrome浏览器,访问:
http://192.168.0.11/camera.html
- 服务器,新终端,运行rviz
rosrun rviz rviz
- 订阅imu和image话题,显示相应的内容
参考:
- http://wiki.ros.org/roslibjs/Tutorials/Publishing%20video%20and%20IMU%20data%20with%20roslibjs
获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号