在VC++中獲取Rplidar數據並使用OpneCV顯示出來

在VC++中獲取Rplidar數據並使用OpneCV顯示出來

最近時間空閒下來想研究下SLAM,正好看到大牛的文章http://www.cnblogs.com/gaoxiang12/p/3695962.html,其中講到如何用kinect深度相機做特徵點匹配,進而算出位移。由於手頭沒有深度相機,想着無非是降個維度,使用2D激光雷達來做位置數據的獲取,然後在網上繼續查找關於激光雷達數據獲取及特徵匹配的文章,然後看到http://blog.csdn.net/renshengrumenglibing/article/details/8596401,雖然不是用的思嵐科技的Rplidar,但是數據應該差異不大,決定從這個文章入手按照作者的套路用Rplidar一步步做數據獲取和展現。

開發環境:Visual Studio 2013

OpenCV版本:OpenCV3.0

文章結構
- 配置OpenCV3.0
- 下載Rplidar sdk
- 創建解決方案並添加sdk項目
- Rplidar數據獲取
- 使用OpenCV顯示數據

先上顯示效果圖:
這裏寫圖片描述

http://blog.csdn.net/renshengrumenglibing/article/details/8596401

配置OpenCV3.0

配置OpenCV花了很長時間,分別嘗試下載OpenCV2.4.9和OpenCV2.4.10,環境變量都配好,跑測試程序均死在後提示“OpenCV 無法啓動此程序,因爲計算機中丟失opencv_core249.dll。請嘗試重新安裝改程序已解決此問題”嘗試各種解決方案無果,抱着試試看的態度下載了OpenCV3.0.0,配置過程參考:http://blog.csdn.net/u010009145/article/details/50756751,最後成功,可以用了

下載Rplidar sdk

在思嵐官網下載sdk和應用手冊 http://www.slamtec.com/cn/Lidar
解壓後可以在tools文件夾中先安裝驅動,並記下設備對應的串口號,我的是com30
設備的初始化和數據獲取,我是直接看的sdk中帶的示例程序ultra_simple,直接看main.cpp很好理解。RPlidar的簡單使用流程如下,這些代碼是從main函數中摘出來的,方便理解

// create the driver instanc
//創建一個RPlidar的實例,一個實例對應一個設備
RPlidarDriver * drv = RPlidarDriver::CreateDriver(RPlidarDriver::DRIVER_TYPE_SERIALPORT);
// make connection... 連接
drv->connect(opt_com_path, opt_com_baudrate;
//startMotor 啓動電機
drv->startMotor();
// start scan...
drv->startScan();
//獲取一次旋轉一週的數據
op_result = drv->grabScanData(nodes, count);
//停止掃描,停止電機,釋放實例
drv->stop();
drv->stopMotor();
RPlidarDriver::DisposeDriver(drv);

如果我們新建一個項目,只需要將路徑\rplidar_sdk.1.5.6\sdk\sdk中的sdk文件夾中文件拷貝出來就可以。

創建解決方案並添加sdk項目

在VC2013中按照以下目錄結構創建解決方案
這裏寫圖片描述

其中rplidar_driver項目是靜態庫,RplidarSLAM項目是win32控制檯程序,創建時選擇空項目;
1. rplidar_driver中目錄結構與sdk文件夾結構一致,然後添加文件即可,在配置-C/C++-附加包含目錄中添加路徑xxx\sdk\src和xxx\sdk\include;xxx是你自己的工程目錄,然後右鍵該項目-生成,不出意外應該可以生成一個dll
2. RplidarSLAM是一個空項目,包含一個main.cpp、opencv_lidar.cpp 和 opencv_lidar.h,項目的配置-C/C++-附加包含目錄中除了第一步中opencv相關的頭文件目錄外,將rplidar的sdk的include路徑xxx\sdk\include也要添加進去;關鍵的一步是要將上面生成的dll庫鏈接到該項目,在配置-鏈接器-附加庫目錄中將上面生成的dll路徑添加進去就可以了
3. 開始編程
opencv_lidar.h

#pragma once
#include <iostream>
#include <cmath>

#include <opencv2\opencv.hpp>
#include <highgui.h> 

#include "rplidar.h" //RPLIDAR standard sdk, all-in-one header

using namespace std;
using namespace cv;
//定義opencv顯示界面大小,rplidar最遠距離是6m,所以方便計算,畫布取6的倍數
static int RadarImageWdith = 600;
static int RadarImageHeight = 600;
//測量點數據結構,這個可以參考應用手冊
struct scanDot {
    _u8   quality;
    float angle;
    float dist;
};

class LidarImage
{
public:
    LidarImage(void);
    ~LidarImage(void);

    vector<scanDot> scan_data; //保存每掃描一週的雷達數據
    float scan_speed;
    void scanData(rplidar_response_measurement_node_t *buffer, size_t count, float frequency);
    void draw(IplImage* RadarImage);
};

opencv_lidar.cpp

#include "opencv_lidar.h"
#define PI 3.141592653

LidarImage::LidarImage(void)
{
}

LidarImage::~LidarImage(void)
{

}
//將掃面的原始數據轉化爲實際距離和角度,這個可以參考ultra_simple
void LidarImage::scanData(rplidar_response_measurement_node_t *buffer, size_t count, float frequency)
{
    scan_data.clear();
    for (int pos = 0; pos < (int)count; ++pos) {
        scanDot dot;
        if (!buffer[pos].distance_q2) continue;

        dot.quality = (buffer[pos].sync_quality >> RPLIDAR_RESP_MEASUREMENT_QUALITY_SHIFT);
        dot.angle = (buffer[pos].angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT) / 64.0f;
        dot.dist = buffer[pos].distance_q2 / 4.0f;
        scan_data.push_back(dot);
    }

    scan_speed = frequency;
}
//將掃描點映射到畫布上
void LidarImage::draw(IplImage* RadarImage)
{
    //RadarImage = cvCreateImage(cvSize(RadarImageWdith,RadarImageHeight),IPL_DEPTH_8U,1);  
    cvZero(RadarImage);
    //在中心加上一個圓心  
    cvCircle(RadarImage, cvPoint(RadarImageWdith / 2, RadarImageHeight / 2), 3, CV_RGB(0, 255, 255), -1, 8, 0);

    int x, y;
    double theta, rho;
    unsigned char * pPixel = 0;
    int halfWidth = RadarImageWdith / 2;
    int halfHeight = RadarImageHeight / 2;

    //此處爲在界面上顯示文字使用了CvFont
    CvFont font;
    cvInitFont(&font, CV_FONT_HERSHEY_COMPLEX, 0.5, 0.5, 0, 1, 8);

    for (int i = 0; i < scan_data.size(); i++)
    {
        scanDot dot;
        dot = scan_data[i];
        theta = dot.angle*PI / 180;
        rho = dot.dist;

        x = (int)(rho*sin(theta) / 20) + halfWidth;
        y = (int)(-rho*cos(theta) / 20) + halfHeight;

        cvCircle(RadarImage, cvPoint(x, y), 1, CV_RGB(0, 255, 0), 1, 8, 3); //每個點用一個小圓表示
    }

    char s[35];
    sprintf_s(s, "Value Count: %d, Scan Speed: %0.2f", scan_data.size(), scan_speed);
    cvPutText(RadarImage, s, cvPoint(50, 50), &font, cvScalar(255, 0, 0));//顯示掃描的有效點個數和掃描頻率
}

main.cpp

#include <stdio.h>
#include <stdlib.h>

#include "rplidar.h" //RPLIDAR standard sdk, all-in-one header
#include "opencv_lidar.h"

#include <iostream>  
#include <cmath>  
#include "io.h"

#ifndef _countof
#define _countof(_Array) (int)(sizeof(_Array) / sizeof(_Array[0]))
#endif

using namespace rp::standalone::rplidar;

bool checkRPLIDARHealth(RPlidarDriver * drv)
{
    u_result     op_result;
    rplidar_response_device_health_t healthinfo;

    op_result = drv->getHealth(healthinfo);
    if (IS_OK(op_result)) { // the macro IS_OK is the preperred way to judge whether the operation is succeed.
        printf("RPLidar health status : %d\n", healthinfo.status);
        if (healthinfo.status == RPLIDAR_STATUS_ERROR) {
            fprintf(stderr, "Error, rplidar internal error detected. Please reboot the device to retry.\n");
            // enable the following code if you want rplidar to be reboot by software
            // drv->reset();
            return false;
        }
        else {
            return true;
        }

    }
    else {
        fprintf(stderr, "Error, cannot retrieve the lidar health code: %x\n", op_result);
        return false;
    }
}

int main(int argc, const char * argv[]) {
    const char * opt_com_path = NULL;
    _u32         opt_com_baudrate = 115200;
    u_result     op_result;

    // read serial port from the command line...
    if (argc>1) opt_com_path = argv[1]; // or set to a fixed value: e.g. "com3" 

    // read baud rate from the command line if specified...
    if (argc>2) opt_com_baudrate = strtoul(argv[2], NULL, 10);


    if (!opt_com_path) {
#ifdef _WIN32
        // use default com port
        opt_com_path = "\\\\.\\com3";
#else
        opt_com_path = "/dev/ttyUSB0";
#endif
    }

    // create the driver instance
    RPlidarDriver * drv = RPlidarDriver::CreateDriver(RPlidarDriver::DRIVER_TYPE_SERIALPORT);

    //creat opencv image
    IplImage* RadarImage = cvCreateImage(cvSize(RadarImageWdith, RadarImageHeight), IPL_DEPTH_8U, 3);

    LidarImage lidarImage;

    if (!drv) {
        fprintf(stderr, "insufficent memory, exit\n");
        exit(-2);
    }


    // make connection...
    if (IS_FAIL(drv->connect(opt_com_path, opt_com_baudrate))) {
        fprintf(stderr, "Error, cannot bind to the specified serial port %s.\n"
            , opt_com_path);
        goto on_finished;
    }



    // check health...
    if (!checkRPLIDARHealth(drv)) {
        goto on_finished;
    }


    // start scan...
    drv->startScan();

    // fetech result and print it out...
    char key;
    cvNamedWindow("Radar", 1);

    while (1) {
        rplidar_response_measurement_node_t nodes[360 * 2];
        size_t   count = _countof(nodes);

        op_result = drv->grabScanData(nodes, count);

        if (IS_OK(op_result)) {
            float frequency = 0;
            drv->getFrequency(nodes, count, frequency);
            lidarImage.scanData(nodes, count, frequency);
            lidarImage.draw(RadarImage);

            cvShowImage("Radar", RadarImage);
            key = cvWaitKey(30);
            if (key == 27)//esc退出  
            {
                break;
            }       
        }
    }

    cvReleaseImage(&RadarImage);
    cvDestroyWindow("Radar");
    // done!
on_finished:
    RPlidarDriver::DisposeDriver(drv);
    return 0;
}

如果不出意外,生成RplidarSLAM.exe後,直接cmd打開命令行,定位到RplidarSLAM的輸出文件夾,輸入RplidarSLAM.exe \.\com30後就可以看到雷達數據顯示界面啦。
然而這只是學習SLAM微不足道的一步,看了很多文章,2DSLAM很多實用GMAPPING實現的,感覺又要轉ROS才能做了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章