Robotics Lab1 —— 基於顏色特征的目標識別與追蹤實驗


Robotics Lab1 —— 基於顏色特征的目標識別與追蹤實驗

由於第一次接觸OpenCV和圖像處理,閱讀代碼和進行實驗時還是有一定的難度。通過他人的講解和查閱相關的資料,有了初步的認識。感覺實驗一已經融合了一些知識、算法和編程技巧,接收起來很零散。是否有基礎的讀物或逐步進階的知識層,來學習圖像處理領域的內容。

1. 環境的安裝和配置

  • Ubuntu16.04系統,計算機攝像頭,OpenCV-2.4.13;
  • 由於網絡問題,沒有通過遠程請求來下載。解壓OpenCV-2.4.13壓縮包,運用Cmake和make命令進行編譯和安裝,安裝過程比較順利,沒有出現什么問題。
  • 需要注意的是,如果Ubuntu系統之前沒有安裝過ROS,則在安裝OpenCV需要安裝一定的依賴包。以防萬一,還是先執行一遍該操作,否則卸載和重裝很麻煩,會遇到很多未知的問題。
  • 安裝依賴包的命令如下:
# 安裝編譯工具
sudo apt-get install build-essential
# 安裝依賴包
sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
# 安裝可選包
sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
  • 開啟終端,進入實驗代碼文件夾,使用python ./video,py命令運行攝像頭測試程序,能夠順利的捕獲視頻。

2. 相關概念和原理理解

從實驗代碼中涉及到的一些相關概念和算法入手,查閱資料,理解學習。如有錯誤,請指正。

灰度圖

[理解] 圖像處理中,用RGB三個分量[0,255]表示真彩色,也就是神經網絡里處理圖像數據時經常說的:3通道。一個像素點的顏色是由RGB三個值來表現的。已知圖片的尺寸信息w*h(像素),則一個像素點矩陣對應三個顏色向量矩陣(R,G,B),分別為w*h大小的矩陣。

而對於灰度圖,三通道的取值均相同(R=B=G),都為0時為黑色,都為255時為白色,0-255區間的值代表不同的亮度等級(灰度值),直觀上看圖像只有黑白兩色。

[作用]

  1. 圖像識別中,需要進行特征提取,梯度是其中最關鍵的因素,因為其意味着邊緣。而灰度圖可以理解為圖像的強度,常用於計算梯度。常用的特征提取方法如HOG、LBP、SIFT等,本質都是統計梯度的信息。
  2. 同類物體的顏色易受光照等因素的影響,從而產生不同的變化,所以RGB圖像難以提供關鍵信息;而灰度圖綜合了真彩色位圖的RGB各通道的信息,與彩色圖對圖像的描述一致,能夠提供有用信息。所以在圖像識別中,通常將彩色圖灰度化,再進行處理和分析。
  3. 灰度圖存儲量小,可以用較小的內存存儲大量的特征信息。
  4. 有時,在圖像處理和識別中,可視化的效果是RGB圖像,但實際輸入和處理的都是灰度圖像。

[灰度化] 一張圖片由一個像素點矩陣構成, 對圖像的處理實際是對像素點矩陣的操作。用矩陣索引的方法表示某個像素點的位置,通過對其三個變量賦值,可以改變像素點的顏色。將圖像灰度化,實際是讓像素點矩陣中的每一個像素點的三變量值相等,此時的值成為灰度值[0-255之間的值]。通常有兩種處理方法:

  1. 灰度化后的R=(處理前的R + 處理前的G +處理前的B)/ 3

    灰度化后的G=(處理前的R + 處理前的G +處理前的B)/ 3

    灰度化后的B=(處理前的R + 處理前的G +處理前的B)/ 3

  2. 在YUV的顏色空間中,Y分量表示點的亮度,而亮度Y與R、G、B三個顏色分量對應:Y=0.3R+0.59G+0.11B,以這個亮度值來表示圖像的灰度值。

    灰度化后的R = 處理前的R * 0.3+ 處理前的G * 0.59 +處理前的B * 0.11

    灰度化后的G = 處理前的R * 0.3+ 處理前的G * 0.59 +處理前的B * 0.11

    灰度化后的B = 處理前的R * 0.3+ 處理前的G * 0.59 +處理前的B * 0.11

一般來說第二種方法效果較好。

[二值化]灰度化后圖像的三通道值雖然相等,但可以有0-255區間的任意值,而二值化就是讓每個像素點的灰度值只有0和255兩個值,直觀上看圖像只有黑與白兩種效果。

對灰度圖進行二值化,對於灰度值究竟轉化為0還是255的問題,引入了閾值。常用的方法有3種:

  1. 對於所有的圖片,均取閾值為127[0-255的中位數],將灰度值<=127的變為0(黑色),灰度值>127的變為255(白色)。優點:計算量小,速度快;缺點:不同的圖片,顏色分布差別很大,不一定適用於中值。
  2. 先對所有像素點的灰度值求平均(avg),然后將每個像素點的灰度值依次與平均值比較,<=avg變為0,>avg變為255
  3. 直方圖法[雙峰法]直方圖認為圖像由前景背景組成,直觀看灰度圖,圖像的前景背景都在其上形成高峰,而雙峰之間的最低谷處為閾值,再將每個像素點的灰度值與閾值比較。
  • [注] 每張圖片都分前景和背景,一般認為靠近相機的為前景。前景目標圖像,涉及目標跟蹤問題,一般流程是檢測——識別——跟蹤,對於目標跟蹤,主要是根據實際問題建立最適合的模型再選取最優的算法。
    一般線性問題可以用卡爾曼濾波算法粒子濾波算法meanshift(利用顏色直方圖,較簡便),但對於遮擋情況處理效果都不是很好;此外還有光流估計等算法。

直方圖

[理解]

  • 直方圖實際上是對圖像的另一種解釋,其引用了統計學中顯示數據分布情況的二維統計圖表,直觀的顯示圖像中灰度的分布情況。
  • 直方圖的橫軸表示灰度的等級(由左至右為由暗到亮),以灰度圖為例,縱軸表示該灰度級的像素點的個數[一般是兩個灰度值區間內的像素點個數]。直方圖在某個亮度區間的凸起越高,即表示在這個亮度區間內的像素越多。[如:如果某個直方圖的凸起主要集中在左側,則說明這張圖像的亮度整體偏低]。
  • 直方圖的橫軸等級還可以選擇彩色圖像R、G、B任意顏色的信息。
  • 一般會將直方圖的縱坐標歸一化到[0,1]區間內,即將灰度級出現的像素個數除以圖像中像素的總數,得到灰度級出現的頻率信息。

[統計直方圖——不加掩模]

  • BINS 對灰度等級進行分組,即划定等級區間。BINS為組數,直觀顯示在直方圖上為矩形塊的個數。OpenCV中用histSize表示。
  • DIMS 表示對圖像數據進行處理時使用的參數數目。如只考慮灰度值,則為1。
  • RANGE 要統計的灰度值范圍,一般為[0-256],即所有的灰度值。
  • OpenCV中,得到的直方圖為一維數組,索引表示顏色值,對應的值表示像素點個數。若為灰度圖,則僅含一個灰度值參數,若為RGB圖像,則可以通過for循環分別得到每個通道的灰度圖,再顯示到同一個二維坐標圖中。
  1. OpenCV方式[較快]
    OpenCV中統計一幅圖像的直方圖函數為:
cv2.calcHist(images; channels; mask; histSize; ranges[, hist[, accumulate]])

其中,mask(掩模)參數用以選擇圖像的某一部分的直方圖,值為None時表示統計整幅圖像。

  1. Numpy方式
# img.ravel() 將圖像轉成一維數組,這里沒有中括號。
hist,bins = np.histogram(img.ravel(), 256, [0,256])
# 一維直方圖常用,速度快
hist=np.bincount(img.ravel(), minlength=256)

[繪制直方圖]

  1. Matplotlib方式[簡單,方便繪制多通道(BGR)的直方圖]
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('home.jpg',0)
plt.hist(img.ravel(),256,[0,256]);
plt.show()
  1. OpenCV方式[復雜]

[掩模(mask)]

  • 掩模圖像的矩陣形狀和原圖像相同,為二維數組,通過全局或局部遮擋來控制圖像的處理區域。
  • 掩模實際上是兩幅圖像間進行的各種位運算操作。
  • 在圖像基本運算的操作函數中,若參數中含有掩模,則原始圖像在運算完之后會與掩模圖像運算。
  • 掩模的作用主要有提取感興趣區屏蔽某些區域結構特征提取。本次實驗用到了掩模的第一個作用:選定感興趣區域(ROI)——
  1. 構建掩模圖像,選定感興趣區域:其為二維數組,設置要統計的區域值為255(白色),其余部分為0(黑色)。這時,僅得到另一個黑白的掩模圖像。將這個掩模圖像傳給統計直方圖的函數。
  2. 與運算:用預先制作的感興趣區掩模與待處理圖像作與運算,使得感興趣區域的像素值不變(有時統一為白色),區域外的像素值為0此時,得到的是划定了一塊區域后的原始圖像,區域外的內容均變為了黑色(類似圖片剪切,但保留了剪切的部分)

[1D直方圖與2D直方圖]

  • 繪制灰度圖的直方圖,僅考慮了圖像的灰度值一個特征,所得的直方圖為1D直方圖
  • 而對於彩色圖像的直方圖,即2D直方圖,需要考慮兩個圖像特征——顏色(Hue)飽和度(Saturation);其橫軸代表S值,縱軸代表H值。
  • 繪制彩色2D直方圖,首先需要將圖像的顏色空間從BGR轉換到HSV。[因為在HSV顏色空間要比在BGR空間中更容易表示一個特定顏色]。函數cv2.calcHist()的參數要進行相應的修改:
  1. channels=[0,1],同時處理H和S兩個通道。
  2. bins=[180,256],按每個灰度值來算時,H通道為180組,S通道為256組。
  3. range=[0,180,0,256],H的取值范圍為0-180,S的取值范圍為0-256.

直方圖均衡化

[理解] 一張圖片的亮度等級可能集中分布在[0-255]區間的某個子區間,而通過某種轉化,重新分配像素值,使一定灰度范圍內的像素數量(在算法實現上用概率表示)大致相同,即將其像素點的個數均勻分布在0-255這個更大的區間(視覺上直方圖呈現拉伸和壓平的效果),會增強圖像的對比度。
圖像對比度增強有直接和間接兩種方式,直方圖拉伸直方圖均衡化是兩種最常見的間接對比度增強方法。
直方圖拉伸通過對比度拉伸對直方圖進行調整,從而“擴大”前景和背景灰度的差別,以達到增強對比度的目的,實現方法有線性和非線性兩種。
直方圖均衡化通過使用累積函數對灰度值進行調整以實現對比度的增強。

[算法原理] 直方圖均衡化實際上是要找到一個累積變換函數,利用這個函數對原圖像的像素個數重新分配區間。該變換函數需要滿足兩個條件:

  1. f(x)在 0<=x<=L−1 上單調遞增(不要求嚴格單調遞增),其中x,L表示灰度級(L=256);保證灰度級次序,即原本暗的仍然暗,原本亮的仍然亮,防止黑白顛倒。
  2. f(x)的范圍是 [0,L−1]使輸入和輸出在同一個區間范圍內變換,方便比較;有時也用[0,1]。

不同問題有不同的直方圖均衡化變換函數,但函數的生成可以“自動化”,即有一套既成的公式,對於不同的值進行套用,下圖描述了公式的推導[要知道數學上離散是連續的一種特例]:

直方圖均衡化推導

由上述過程可知,對於一個輸入圖像,只需統計它之中各灰度值出現的概率Pf,然后生成映射函數 均衡化映射函數(連續形式).png

  • 圖像中,灰度值是離散的,則可以用求和代替積分,差分代替微分,則函數定義改為 原圖灰度值概率定義.png定義2.png ,即圖像的累積直方圖CDF
    變換函數為 均衡化映射函數(離散).png ,其中h(xi)表示直方圖中每個灰度級像素的個數, w和h分別表示圖像的寬和高。
  • 在灰度值離散的情況下,原始和均衡化后的直方圖灰度級均為整數,則必須對映射結果取整,這導致變換后的圖像中各灰度值出現的概率未必相等。但是可以確定的是: g 的灰度級在一定程度上比 f 更分散了。

[作用]

  1. 直方圖均衡化可以增強圖像的對比度,常用來增強局部對比度。
  2. 直方圖均衡化對於背景和前景太亮或太暗的圖像處理效果更好。
  3. 直方圖均衡化通常用在訓練圖片的預處理中,來使所有圖片具有相同的亮度條件。
  4. 直方圖均衡化對處理的數據不加選擇,其可能增強噪音,而降低有用信號的對比度。

直方圖反向投影

[理解——△重要]

OpenCV中文網站 對反向投影的講解比較詳細,也比較容易理解。

  • 直方圖反向投影通常應用在對比兩幅圖像中,[如在測試圖像中找到含示例(模型)圖像顏色的區域]。對比兩幅圖像,實際上是提取圖像中的某個特征,提取特征的過程就是反向投影的過程。
  • 有示例圖像和目標圖像,分別統計出它們的直方圖,[一般使用顏色直方圖,即用H分量作直方圖。因為這更易於圖像分割],再由直方圖得到反向投影矩陣。
  • 對原來的直方圖進行反向投影,實際上是將原圖像簡單化:首先對亮度值0-255划分區間(注意模型和目標的直方圖划分區間要相同),找到目標圖像的像素值所在的區間,再去模型直方圖對應區間找到BINS的值,用該值代替目標圖像對應區間內的所有像素值;
    [這樣就建立了測試圖像和目標特征的聯系]。最后得到的圖像矩陣和原矩陣大小相同,但其中的值種類更少。[一般將0-255划分為幾個區間,就有幾個值]。
  • 由反向投影的過程可以看出,若在原圖像的直方圖區間內點越多,替換的像素值就越大,而越大的值在亮度等級上越高,表現在反向投影矩陣中就越亮。
  • 結果為需要尋找的特征區域在反向投影圖中越亮,其他區域越暗。最后得到的顏色概率分布圖是一個灰度圖像。

[作用]

  • 反向投影一般給定某個特征,計算某一特征的直方圖模型,然后使用模型去尋找測試圖像中存在的該特征。
  • 可以通過直方圖反向投影來實現圖像分割,背景與對象分離,對已知對象位置進行定位等。
  • 常應用於模式匹配、對象識別、視頻跟蹤。

[算法實現]

OpenCV-Python-Toturial中文版

  • 不太理解用圓盤形卷積核對其做卷操作,最后使用閾值進行二值化的原因和含義。
  • OpenCV實現如下:
import cv2
import numpy as np
roi = cv2.imread('tar.jpg')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
target = cv2.imread('roi.jpg')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)

# calculating object histogram
roihist = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )

# 歸一化:原始圖像,結果圖像,映射到結果圖像中的最小值,最大值,歸一化類型
#cv2.NORM_MINMAX 對數組的所有值進行轉化,使它們線性映射到最小值和最大值之間
# 歸一化之后的直方圖便於顯示,歸一化之后就成了 0 到 255 之間的數了。
cv2.normalize(roihist,roihist,0,255,cv2.NORM_MINMAX)
dst = cv2.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)

# 此處卷積可以把分散的點連在一起
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
dst=cv2.filter2D(dst,-1,disc)

# threshold and binary AND
ret,thresh = cv2.threshold(dst,50,255,0)

# 別忘了是三通道圖像,因此這里使用 merge 變成 3 通道
thresh = cv2.merge((thresh,thresh,thresh))

# 按位操作
res = cv2.bitwise_and(target,thresh)
res = np.hstack((target,thresh,res))
cv2.imwrite('res.jpg',res)

# 顯示圖像
cv2.imshow('1',res)
cv2.waitKey(0)

CamShift算法

  • 典型的目標跟蹤算法。CamShift算法是MeanShift算法的擴展,兩者常常與直方圖反向投影相結合,從反向投影得到顏色概率分布圖入手。

[meanshift算法]
其過程為:

  1. 在顏色概率分布圖中選擇搜索窗口:窗口的初始位置;窗口的類型(均勻、多項式、指數或高斯類型);窗口的形狀(對稱的、歪斜的、可能旋轉的、圓形或矩形);窗口的大小(超出窗口大小則被截去)。
  2. 計算窗口的重心:即x,y分別的一階距與零階距之比。
  3. 調整搜索窗的大小。
  4. 移動搜索窗的中心到計算出的質心。
  5. 返回第2步,直到搜索窗的中心與質心間的移動距離小於預設的固定閾值,或者循環運算的次數達到某一最大值,則停止計算。

[CamShift算法]

  • CamShift算法將meanshift算法擴展到連續圖像序列,其將視頻的所有幀做meanshift運算,並將上一幀的結果(搜索窗的大小和重心),作為下一幀meanshift算法搜索窗的初始值。
    迭代此過程,實現對目標的跟蹤。其過程為:
  1. 初始化搜索窗;
  2. 計算搜索窗的顏色概率分布(反向投影);
  3. 執行meanshift算法,獲取搜索窗新的大小和位置;
  4. 在下一幀視頻圖像中用3中的值重新初始化搜索窗的大小和位置,再跳轉到2繼續進行。

[算法性能]

優點: camshift能有效解決目標變形和遮擋的問題,對系統資源要求不高,時間復雜度低,在簡單背景下能夠取得良好的跟蹤效果。

缺點: 當背景較為復雜,或者有許多與目標顏色相似像素干擾的情況下,會導致跟蹤失敗。因為它單純的考慮顏色直方圖,忽略了目標的空間分布特性,所以這種情況下需加入對跟蹤目標的預測算法。

主要代碼分析

關於函數的用法以及涉及的原理寫到了代碼的注釋里,這里整理一下程序的實現思路。

  • Lab1是“關於顏色特征的目標識別和與追蹤”,則根據示例圖的顏色信息(純色),從視頻圖像中選定的初始搜索區域內進行目標顏色的檢測,識別到之后使用反向投影和CamShift算法控制搜索框移動到目標區域,繪制紅色的橢圓框進行追蹤。
  1. [預處理]首先,獲取視頻幀圖像,將其從BGR顏色空間轉到HSV顏色空間,這樣可以忽略光照等亮度信息的影響,並設置視頻幀圖像的掩模。
roi = self.roi    # 獲取感興趣的例圖
        self.start()      # 開啟檢測

        while True:  # 在輸入錯誤的情況下仍然可以繼續循環
#         for frame in self.cam.capture_continuous(self.rCa, format='bgr', use_video_port=True):
            ret, self.frame = self.cam.read()      # 讀取視頻幀
#             self.frame = frame.array
            vis = self.frame.copy()
#             vis = copy.deepcopy(self.frame)
            hsv = cv2.cvtColor(self.frame, cv2.COLOR_BGR2HSV)       # 將當前幀從RGB格式轉換為HSV格式

            # 獲得當前視頻幀hsv圖像的掩膜
            """
               掩膜的概念:圖像處理中,選定一塊圖像、圖形或物體,對處理的圖像(全部或局部)進行遮擋,來控制圖像處理的區域或處理過程
               這里遮擋處理的圖像是當前攝像頭獲取的視頻幀
            """
             # 三個維度對應H、S、V,numpy數組設定了三個維度分別的范圍,在mask區域里設為255,其它區域設為0,實際上變為黑白圖;
			 # 這里避免由於低光引起的錯誤值,使用cv.inRange()函數丟棄低光值;即選定的范圍是掩模,是后續要進行處理的區域。
             mask = cv2.inRange(hsv, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
  1. 選定搜索區域后,設置當前窗口,開始檢測。這時獲得示例圖的HSV圖像和設置掩模(與視頻幀的掩模相對應)。[輔助處理] 為了增強圖像的對比度,方便檢測和追蹤,此時繪制例圖的H通道信息的一維直方圖,並進行均衡化。
if self.selection:    # 若開啟檢測
#                 x0, y0, x1, y1 = 220, 110, 358, 245
               x0, y0, x1, y1 = self.selection     # 獲取選擇的區域四個坐標
               self.track_window = (x0, y0, x1-x0, y1-y0)     # 根據坐標設置當前跟蹤窗口的形狀

#                 hsv_roi = hsv[y0:y1, x0:x1]
#                 mask_roi = mask[y0:y1, x0:x1]
               hsv_roi = cv2. cvtColor(roi,cv2. COLOR_BGR2HSV)     # 例圖的HSV格式圖像
               mask_roi = cv2.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))   # 例圖的掩膜(與當前視頻幀的掩膜對應)

               #僅以Hue值為特征,繪制例圖的一維直方圖,分為16個區間
               hist = cv2.calcHist( [hsv_roi], [0], mask_roi, [16], [0, 180] )    # mask_roi用來標記例圖中要查找的顏色的區域(此示例不必要,整張圖顏色相同)

       #二維直方圖
#                 hist = cv2.calcHist( [hsv_roi], [0,2],None, [180,256], [0, 180,0 , 255] )

               cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX);    # 直方圖均衡化,[0,255]表示均衡后的范圍

               # 新數組的shape屬性應與原來的數組一致,即新數組元素數量與原數組元素數量要相等。
       # 當其中一個參數為-1時,函數會根據另一個參數的維度計算出數組的另外一個shape屬性值。
       # 這里表示將hist矩陣變為一列(按行展開)。
       self.hist = hist.reshape(-1)

               #二維直方圖顯示
#                 plt.imshow(hist,interpolation = 'nearest')
#                 plt.show()
               self.show_hist()

       # 我的理解是這一部分使視頻圖像中感興趣的區域為白色,其余為黑色。
               vis_roi = vis[y0:y1, x0:x1]
               cv2.bitwise_not(vis_roi, vis_roi)
               vis[mask == 0] = 0
  1. 接下來到了追蹤目標的核心代碼,使用例圖的直方圖對視頻圖像做反向投影,得到顏色概率分布圖。CamShift算法從這個顏色概率分布圖開始,進行一系列計算,根據得到的向量控制搜索框向目標區域移動。
    每一幀根據上一幀的結果動態調整繪制的橢圓追蹤框的大小、形狀和方向。
if self.tracking_state == 1:    # 如果檢測到了圖像中存在的感興趣顏色區域
                self.selection = None       # 追蹤到對象之后,不用再設置搜索區域
                """
                   反向投影:重置視頻幀的像素點的值后,最亮的即值最大的,在例圖的灰度直方圖中出現的個數最多,極有可能是要追蹤的目標;
                            最后相當於得到一個概率圖
                """
                prob = cv2.calcBackProject([hsv], [0], self.hist, [0, 180], 1)     # 區分顏色的是H(范圍為0-180),以例圖的直方圖作反向投影,得到顏色概率分布圖

                prob &= mask     # 使要尋找的顏色區域更亮

                term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )    # 迭代到10次或誤差小於1,搜索結束

                """
                   CamShift算法:對meanShift進行改進,可以自適應的調整橢圓的大小和角度
                                 追蹤目標顏色質心
                """
                track_box, self.track_window = cv2.CamShift(prob, self.track_window, term_crit)
#                 if track_box[0][1] <= 240:
#             self.ser.write(str(int(track_box[0][0])-320) + " " + str(int(track_box[0][1])-240))
#             print str(int(track_box[0][0])-320) + " " + str(int(track_box[0][1])-240)
                if track_box[1][1] <= 1:   # 目標距離太遠,重置算法,重新搜索
                    self.tracking_state = 0
                    self.start()
                else:
                    if self.show_backproj:
                        vis[:] = prob[...,np.newaxis]
                    try:
					    '''
                        track_box: [[center, axes], [angle, startAngle], endAngle]
                        '''
                        cv2.ellipse(vis, track_box, (0, 0, 255), 2)   # 繪制紅色的橢圓標記
#                         print track_box
                        a = str(track_box[0][0])+" "+str(track_box[0][1])+" "+str(round(track_box[1][0],2))\
                                       +" "+str(round(track_box[1][1],2))+" "+str(round(track_box[2],2))+"\r\n"
                        print a
#                         self.ser.write(a)
                    except: print track_box


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM