1. 程式人生 > >VREP中的二維激光雷達

VREP中的二維激光雷達

upd 參考 class nan clip 模型 高速 then 世界坐標系

  目前,輪式機器人的研究中已經大量使用激光雷達輔助機器人的避障導航,考慮到使用成本,一般二維激光雷達使用較多,如下圖。由於只能掃描一個平面,如果想用二維激光雷達獲取環境三維點雲,則需要通過移動機器人或加裝機械結構提供第三個維度的支持。

技術分享

  激光雷達掃描時可以想象成將超聲波傳感器發出的聲波替換為激光並高速回轉掃描,如此就能大概構建出附近的物體輪廓,這個過程非常像潛艇上使用聲納探測周圍物體。當然,由於激光雷達使用激光而不是聲波,它的探測過程不僅極短,而且能彌補聲波廣角發散的缺點(激光不易發散,錐度角很小)。激光雷達工作時會先在當前位置發出激光並接收反射光束,解析得到距離信息,而後激光發射器會轉過一個角度分辨率對應的角度再次重復這個過程。限於物理及機械方面的限制,激光雷達通常會有一部分“盲區”。使用激光雷達返回的數據通常可以描繪出一幅極坐標圖,極點位於雷達掃描中心,0-360°整周圓由掃描區域及盲區組成。在掃描區域中激光雷達在每個角度分辨率對應位置解析出的距離值會被依次連接起來,這樣,通過極坐標表示就能非常直觀地看到周圍物體的輪廓,激光雷達掃描範圍示意圖可以參見下圖。
技術分享

  激光雷達通常有四個性能衡量指標:測距分辨率、掃描頻率(有時也用掃描周期)、角度分辨率及可視範圍。測距分辨率衡量在一個給定的距離下測距的精確程度,通常與距離真實值相差在5-20mm;掃描頻率衡量激光雷達完成一次完整掃描的快慢,通常在10Hz及以上;角度分辨率直接決定激光雷達一次完整掃描能返回多少個樣本點;可視範圍指激光雷達完整掃描的廣角,可視範圍之外即為盲區。

  • 使用視覺傳感器模擬激光雷達

  為了模擬二維激光雷達,將視覺傳感器設為透視模型並只獲取深度信息。near clipping plane和far clipping plane設置為激光雷達的最小/最大掃描距離;視場角Persp. angle設為雷達可視範圍(角度);將Y軸分辨力率設為1,X軸分辨率可根據需求設置(分辨率越大結果越精確,分辨率過低可能會造成某些尺寸小的物體無法被探測到)。

技術分享  視覺傳感器filter設置如下,其中加入Extract coordinates from work image用來提取深度圖像中坐標信息,這一環節返回的數據可以通過函數simReadVisionSensor來獲取。參數設置界面上的Point count along X/Y設為X/Y軸分辨率,即調用一次相關API函數可以讀取包含256個像素點的坐標信息的數據:

技術分享

  為了顯示激光雷達“看到”的環境,可以通過Graph來記錄用戶自定義數據流:按照下圖所示在數據類型欄中選擇user-defined,將自定義數據名分別修改為"x"和"y"用來記錄障礙物相對於激光雷達的位置,並通過函數simSetGraphUserData

(graphHandle, dataStreamName, data)來設置自定義數據的值。

技術分享

  Graph:

技術分享
if (sim_call_type==sim_childscriptcall_initialization) then

    -- Put some initialization code here
    graphHandle = simGetObjectAssociatedWithScript(sim_handle_self)

end




if (sim_call_type==sim_childscriptcall_sensing) then

    -- Put your main SENSING code here
    simResetGraph(graphHandle)
    
    data = simGetStringSignal("UserData")
    if data then
        measuredData = simUnpackFloatTable(data)
        for i=1,#measuredData/2,1 do
            simSetGraphUserData(graphHandle,x,measuredData[2*(i-1)+1])
            simSetGraphUserData(graphHandle,y,measuredData[2*(i-1)+2])

            simHandleGraph(graphHandle,0) 
        end
    end

end



if (sim_call_type==sim_childscriptcall_cleanup) then

    -- Put some restoration code here
    simResetGraph(graphHandle)

end


--[[
Normally, simHandleGraph is called for you, in the main script. In that case, you can record one value in each 
simulation step. When you explicitely handle the graph, then you have the possibility to record more than one 
new graph point in each simulation step.
--]]
View Code

  Vision_sensor:

技術分享
if (sim_call_type==sim_childscriptcall_initialization) then

    -- Put some initialization code here
    visionSensor1Handle = simGetObjectHandle("Vision_sensor")
    red={1,0,0}
    points=simAddDrawingObject(sim_drawing_points,5,0,-1,1000,red,nil,red,red)
end


if (sim_call_type==sim_childscriptcall_sensing) then

    -- Put your main SENSING code here
    measureData = {}
    m1=simGetObjectMatrix(visionSensor1Handle,-1)
    r,t1,u1=simReadVisionSensor(visionSensor1Handle)
    
    if u1 then   

        for j=0,u1[2]-1,1 do      -- point count along Y
            for i=0,u1[1]-1,1 do  -- point count along X
                w=2+4*(j*u1[1]+i) -- index
                v1=u1[w+1]        -- x
                v2=u1[w+2]        -- y
                v3=u1[w+3]        -- z
                v4=u1[w+4]        -- dist

                if(v4 < 0.99) then
                   --[[
                    p={v1,v2,v3}
                    p=simMultiplyVector(m1,p) -- convert point from visionSensor1 coordinates to world coordinates
                    simAddDrawingObjectItem(points, p)
                    --]]
                    table.insert(measureData, v1)
                    table.insert(measureData, v3)
                end

            end
        end

        stringData = simPackFloats(measureData) -- Packs a table of floating-point numbers into a string
        simSetStringSignal("UserData", stringData)
    end

end


if (sim_call_type==sim_childscriptcall_cleanup) then

    -- Put some restoration code here
    simRemoveDrawingObject(points)

end
View Code

  在場景中加入浮動窗口,並將Graph與窗口關聯,切換到X-Y曲線顯示(取消坐標軸自動縮放,坐標軸比例設為1:1)

技術分享

  • SICK TiM310

  VREP模型瀏覽器的components/sensors目錄中包含多種激光雷達,其中德國SICK傳感器推出的迷你激光測量系統TiM310可視範圍為270°,角度分辨率為1°,探測距離為0.05m~4m,掃描頻率15Hz,功耗4W。

技術分享

operating range diagram

  由於是用視覺傳感器來模擬激光雷達,因此270°的可視範圍對於一般視覺傳感器來說太大(普通廣角鏡頭視角在60-84度,超廣角鏡頭的視角為94-118度,魚眼鏡頭視角在220-230度),可以拆分為2個視角135°的視覺傳感器來湊成270°:

技術分享

  視角參數和遠近剪切平面設置如下:

技術分享

  激光雷達獲取的位置數據要在參考坐標系SICK_TiM310_ref中描述,而simReadVisionSensor獲取的像素點坐標是相對於視覺傳感器坐標系的,因此需要使用矩陣變換將其轉換到SICK_TiM310_ref參考系中。用畫筆在場景中畫出激光雷達的掃描線,需要得知線段起點和終點坐標,這一坐標是在世界坐標系下描述的,因此一共涉及3個坐標系之間的變換。

技術分享
if (sim_call_type==sim_childscriptcall_initialization) then 
    visionSensor1Handle=simGetObjectHandle("SICK_TiM310_sensor1")
    visionSensor2Handle=simGetObjectHandle("SICK_TiM310_sensor2")
    joint1Handle=simGetObjectHandle("SICK_TiM310_joint1")
    joint2Handle=simGetObjectHandle("SICK_TiM310_joint2")
    sensorRefHandle=simGetObjectHandle("SICK_TiM310_ref")  -- the base of SICK LiDAR

    maxScanDistance=simGetScriptSimulationParameter(sim_handle_self,maxScanDistance)
    if maxScanDistance>1000 then maxScanDistance=1000 end
    if maxScanDistance<0.1 then maxScanDistance=0.1 end
    simSetObjectFloatParameter(visionSensor1Handle,sim_visionfloatparam_far_clipping,maxScanDistance)
    simSetObjectFloatParameter(visionSensor2Handle,sim_visionfloatparam_far_clipping,maxScanDistance)
    maxScanDistance_=maxScanDistance*0.9999

    scanningAngle=simGetScriptSimulationParameter(sim_handle_self,scanAngle)
    if scanningAngle>270 then scanningAngle=270 end
    if scanningAngle<2 then scanningAngle=2 end
    scanningAngle=scanningAngle*math.pi/180
    simSetObjectFloatParameter(visionSensor1Handle,sim_visionfloatparam_perspective_angle,scanningAngle/2)
    simSetObjectFloatParameter(visionSensor2Handle,sim_visionfloatparam_perspective_angle,scanningAngle/2)

    simSetJointPosition(joint1Handle,-scanningAngle/4)
    simSetJointPosition(joint2Handle,scanningAngle/4)
    red={1,0,0}
    lines=simAddDrawingObject(sim_drawing_lines,1,0,-1,1000,nil,nil,nil,red)

    if (simGetInt32Parameter(sim_intparam_program_version)<30004) then
        simDisplayDialog("ERROR","This version of the SICK sensor is only supported from V-REP V3.0.4 and upwards.&&nMake sure to update your V-REP.",sim_dlgstyle_ok,false,nil,{0.8,0,0,0,0,0},{0.5,0,0,1,1,1})
    end
end 

if (sim_call_type==sim_childscriptcall_cleanup) then 
    simRemoveDrawingObject(lines)
end 

if (sim_call_type==sim_childscriptcall_sensing) then 
    measuredData={}
    
    if notFirstHere then
        -- We skip the very first reading
        simAddDrawingObjectItem(lines,nil)
        showLines=simGetScriptSimulationParameter(sim_handle_self,showLaserSegments)

        r,t1,u1=simReadVisionSensor(visionSensor1Handle)
        r,t2,u2=simReadVisionSensor(visionSensor2Handle)
    
        m1=simGetObjectMatrix(visionSensor1Handle,-1)
        m01=simGetInvertedMatrix(simGetObjectMatrix(sensorRefHandle,-1)) 
        m01=simMultiplyMatrices(m01,m1)  -- transformation matrix between base and visionSensor

        m2=simGetObjectMatrix(visionSensor2Handle,-1)
        m02=simGetInvertedMatrix(simGetObjectMatrix(sensorRefHandle,-1))
        m02=simMultiplyMatrices(m02,m2)

        if u1 then
            p={0,0,0}
            p=simMultiplyVector(m1,p) -- convert the origin of visionSensor1 coordinates to world coordinates
            t={p[1],p[2],p[3],0,0,0}

            for j=0,u1[2]-1,1 do      -- point count along Y
                for i=0,u1[1]-1,1 do  -- point count along X
                    w=2+4*(j*u1[1]+i) -- index
                    v1=u1[w+1]        -- coordinate x of detected point(Coordinates are relative to the vision sensor position/orientation.)
                    v2=u1[w+2]        -- coordinate y of detected point
                    v3=u1[w+3]        -- coordinate z of detected point
                    v4=u1[w+4]        -- distance to detected point
                    if (v4<maxScanDistance_) then
                        p={v1,v2,v3}
                        p=simMultiplyVector(m01,p) -- describe position in LiDAR base coordinates
                        table.insert(measuredData,p[1])
                        table.insert(measuredData,p[2])
                        table.insert(measuredData,p[3])
                    end
                    if showLines then  -- draw laser line
                        p={v1,v2,v3}
                        p=simMultiplyVector(m1,p) -- convert point from visionSensor1 coordinates to world coordinates
                        t[4]=p[1]
                        t[5]=p[2]
                        t[6]=p[3]
                        simAddDrawingObjectItem(lines,t)
                    end
                end
            end
        end
        if u2 then
            p={0,0,0}
            p=simMultiplyVector(m2,p)
            t={p[1],p[2],p[3],0,0,0}
            for j=0,u2[2]-1,1 do
                for i=0,u2[1]-1,1 do
                    w=2+4*(j*u2[1]+i)
                    v1=u2[w+1]
                    v2=u2[w+2]
                    v3=u2[w+3]
                    v4=u2[w+4]
                    if (v4<maxScanDistance_) then
                        p={v1,v2,v3}
                        p=simMultiplyVector(m02,p)
                        table.insert(measuredData,p[1])
                        table.insert(measuredData,p[2])
                        table.insert(measuredData,p[3])
                    end
                    if showLines then
                        p={v1,v2,v3}
                        p=simMultiplyVector(m2,p)
                        t[4]=p[1]
                        t[5]=p[2]
                        t[6]=p[3]
                        simAddDrawingObjectItem(lines,t)
                    end
                end
            end
        end
    end
    notFirstHere=true
    
    -- measuredData now contains all the points that are closer than the sensor range
    -- For each point there is the x, y and z coordinate (i.e. 3 number for each point)
    -- Coordinates are expressed relative to the sensor frame.
    -- You can access this data from outside via various mechanisms. The best is to first
    -- pack the data, then to send it as a string. For example:
    --
    -- 
    -- data=simPackFloatTable(measuredData)
    -- simSetStringSignal("measuredDataAtThisTime",data)
    --
    -- Then in a different location:
    -- data=simGetStringSignal("measuredDataAtThisTime")
    -- measuredData=simUnpackFloatTable(data)
    --
    --
    -- Of course you can also send the data via tubes, wireless (simTubeOpen, etc., simSendData, etc.)
    --
    -- Also, if you send the data via string signals, if you you cannot read the data in each simulation
    -- step, then always append the data to an already existing signal data, e.g.
    --
    -- 
    -- data=simPackFloatTable(measuredData)
    -- existingData=simGetStringSignal("measuredDataAtThisTime")
    -- if existingData then
    --     data=existingData..data
    -- end
    -- simSetStringSignal("measuredDataAtThisTime",data)
end 
View Code

技術分享

  獲得測量數據後,可以使用函數將包含坐標值的一維列表打包成字符串,並設置字符串信號。在另一個需要使用傳感器數據的腳本中讀取字符串信號,然後將其解壓成列表:

data = simPackFloatTable(measuredData)
simSetStringSignal("measuredDataAtThisTime", data)

-- Then in a different location:
data = simGetStringSignal("measuredDataAtThisTime")
measuredData = simUnpackFloatTable(data)

參考:

sick tim310激光安全掃描器

LabVIEW Robotics中如何借助二維激光雷達感知三維場景

Scatter Plot

VREP中的二維激光雷達