)
1. 為什么我要做這個桌面軟件做視覺算法的人都有一個共同的痛點模型訓練得再好最終要落地給非技術(shù)用戶使用的時候總不能讓對方去敲命令行。我前前后后做過好幾個檢測項目每次交付的時候最頭疼的就是怎么讓用戶能直觀地看到檢測結(jié)果、能自己切換視頻源、能導出數(shù)據(jù)。后來我干脆花了兩周時間基于YOLOv8做了一套交通目標檢測與車輛軌跡分析的桌面軟件把檢測、跟蹤、軌跡繪制、數(shù)據(jù)導出全部打包進一個圖形界面里。這個軟件解決的核心問題很明確讓交通場景下的車輛檢測和軌跡分析變得開箱即用。你不需要懂Python不需要配環(huán)境打開軟件選一段視頻或者接一個攝像頭它就能實時框出車輛、標出軌跡、統(tǒng)計流量。適合做智能交通方向的學生做課程設計也適合做安防監(jiān)控的工程人員快速驗證方案更適合想學YOLOv8落地部署的開發(fā)者拿來當參考模板。整個項目我采用的是YOLOv8做目標檢測 backbone配合ByteTrack做多目標跟蹤再用自定義的軌跡管理模塊來繪制車輛運動路徑。界面用的是PyQt5因為它在桌面端足夠成熟控件豐富和OpenCV的兼容性也好。下面我會把整個項目的設計思路、核心代碼、踩過的坑全部拆開講清楚你照著做基本能復現(xiàn)一個可用的版本。2. 整體架構(gòu)與技術(shù)選型拆解2.1 為什么選YOLOv8而不是其他檢測器目標檢測這個環(huán)節(jié)我對比過Faster R-CNN、YOLOv5、YOLOv7和YOLOv8。Faster R-CNN精度確實不錯但推理速度在普通顯卡上跑實時視頻流很吃力兩階段檢測器的天然劣勢擺在那里。YOLOv5雖然成熟但Ultralytics在v8版本里做了很多工程上的優(yōu)化比如新的C2f模塊、Anchor-Free檢測頭、Task-Aligned Assigner這些改進在保持速度的同時把精度又往上推了一截。更關(guān)鍵的是YOLOv8的生態(tài)太方便了。ultralytics這個庫把訓練、驗證、推理、導出全部封裝成了幾行代碼的事而且支持ONNX、TensorRT、OpenVINO等多種部署格式。我實測下來在RTX 3060上用YOLOv8nnano版本跑640x640的輸入推理速度能到80FPS以上完全滿足實時視頻處理的需求。如果你對精度要求更高換成YOLOv8m或者YOLOv8l也可以速度會降一些但依然可用。選模型規(guī)模的時候有個經(jīng)驗公式如果你處理的是1080p視頻車輛目標比較大YOLOv8n就夠了如果是遠處的小目標或者密集車流建議上YOLOv8s或YOLOv8m。我一般先用nano版本跑通流程再根據(jù)實際效果決定要不要換大模型。2.2 跟蹤算法為什么用ByteTrack檢測只能告訴你每一幀里車在哪里但軌跡分析需要知道同一輛車在不同幀之間的對應關(guān)系這就是多目標跟蹤要解決的問題。常見的跟蹤方案有SORT、DeepSORT、ByteTrack和OC-SORT。SORT速度快但ID切換太頻繁DeepSORT加了外觀特征匹配效果好一些但需要額外的ReID模型推理開銷大。ByteTrack的核心思路很巧妙它把檢測框按置信度分成高分組和低分組先用高分組做匹配再用低分組去補救那些漏檢的目標。這樣一來即使某輛車在某幾幀被遮擋導致置信度下降ByteTrack依然能通過低分檢測框把它找回來ID保持得相當穩(wěn)定。我實際測試下來在交通場景里ByteTrack的ID Switch次數(shù)比SORT少了大概60%而且它不需要額外的外觀模型速度幾乎和SORT一樣快。對于車輛軌跡分析這種場景ID穩(wěn)定性直接決定了軌跡的連貫性所以ByteTrack是性價比最高的選擇。2.3 桌面端為什么選PyQt5界面框架的選擇其實糾結(jié)了一陣。Tkinter太簡陋做出來的界面像上個世紀的軟件Electron太重為了一個檢測軟件裝個Chromium沒必要PyQt5剛好在中間控件豐富、文檔齊全、和OpenCV的QImage轉(zhuǎn)換也很順暢。PyQt5的信號槽機制特別適合這種多線程場景。視頻讀取和推理放在子線程里跑主線程只負責界面刷新通過信號槽傳遞檢測結(jié)果不會出現(xiàn)界面卡死的情況。而且PyQt5支持QSS樣式表稍微調(diào)一下就能做出比較現(xiàn)代的深色主題界面用戶體驗比命令行好太多。2.4 整體數(shù)據(jù)流設計整個軟件的數(shù)據(jù)流是這樣的視頻源文件或攝像頭→ 幀讀取 → YOLOv8推理 → ByteTrack跟蹤更新 → 軌跡管理器更新軌跡 → 繪制檢測框和軌跡 → 界面顯示 數(shù)據(jù)記錄。這里有個關(guān)鍵的設計決策推理和繪制分離。推理線程只負責產(chǎn)出檢測結(jié)果和跟蹤ID繪制和界面更新放在主線程或者單獨的渲染線程里。這樣做的好處是即使推理偶爾卡頓界面也不會完全凍結(jié)。我用的是QThread加信號槽的方式推理線程每處理完一幀就發(fā)一個信號帶著結(jié)果數(shù)據(jù)主線程收到后更新畫面。軌跡管理模塊是我自己寫的核心是一個字典結(jié)構(gòu)key是跟蹤IDvalue是該ID對應的軌跡點列表。每幀更新時把當前幀檢測到的ID和位置追加進去同時清理掉超過一定幀數(shù)沒有更新的軌跡。軌跡繪制用OpenCV的cv2.polylines可以設置不同顏色區(qū)分不同車輛。3. 核心模塊的詳細實現(xiàn)3.1 YOLOv8推理模塊的封裝Ultralytics的API用起來很簡單但直接在主線程里調(diào)用model.predict()會阻塞界面。我的做法是封裝一個DetectionWorker類繼承自QThread在run方法里循環(huán)讀取幀并推理。from ultralytics import YOLO import cv2 import numpy as np class DetectionWorker(QThread): frame_ready pyqtSignal(np.ndarray, list, list) def __init__(self, source, model_pathyolov8n.pt): super().__init__() self.source source self.model YOLO(model_path) self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break results self.model.track( frame, persistTrue, trackerbytetrack.yaml, classes[2, 3, 5, 7], # car, motorcycle, bus, truck conf0.4, iou0.5, verboseFalse ) boxes results[0].boxes if boxes.id is not None: ids boxes.id.cpu().numpy().astype(int) xyxy boxes.xyxy.cpu().numpy().astype(int) clss boxes.cls.cpu().numpy().astype(int) self.frame_ready.emit(frame, xyxy, ids, clss) else: self.frame_ready.emit(frame, [], [], []) cap.release()這里有幾個細節(jié)值得說。classes[2,3,5,7]是COCO數(shù)據(jù)集里car、motorcycle、bus、truck的類別索引只檢測這幾類可以過濾掉行人、紅綠燈等無關(guān)目標減少后處理負擔。persistTrue讓跟蹤器在幀之間保持狀態(tài)這是ByteTrack能跨幀跟蹤的關(guān)鍵。conf0.4是我調(diào)出來的經(jīng)驗值太低會引入大量誤檢太高會漏掉遠處的小車。注意model.track()第一次調(diào)用時會自動加載跟蹤器配置如果你用的是自定義數(shù)據(jù)集訓練的模型類別索引要改成你自己數(shù)據(jù)集的對應值。3.2 ByteTrack跟蹤器的參數(shù)調(diào)優(yōu)ByteTrack的配置文件在ultralytics的cfg/trackers/bytetrack.yaml里默認參數(shù)對大多數(shù)場景夠用但交通場景有幾個地方可以微調(diào)。track_high_thresh默認0.5這是高分檢測框的閾值。交通場景里車輛通常比較清晰我調(diào)到0.6讓匹配更嚴格。track_low_thresh默認0.1這是低分框的補救閾值保持默認就行。new_track_thresh默認0.6新軌跡的創(chuàng)建閾值我調(diào)到0.7避免誤檢產(chǎn)生假軌跡。track_buffer默認30意思是軌跡丟失后保留30幀再刪除交通場景里車輛被遮擋的時間可能更長我調(diào)到50。這些參數(shù)沒有絕對的最優(yōu)值需要根據(jù)你的視頻場景來調(diào)。我的建議是先用默認參數(shù)跑一段觀察ID Switch的情況如果切換頻繁就提高track_high_thresh和new_track_thresh如果軌跡容易斷就增大track_buffer。3.3 軌跡管理器的設計與實現(xiàn)軌跡管理器是整個軟件的核心邏輯之一它決定了軌跡怎么存、怎么畫、怎么清理。我的設計是一個TrajectoryManager類內(nèi)部維護一個字典from collections import defaultdict, deque class TrajectoryManager: def __init__(self, max_length60, max_age30): self.trajectories defaultdict(lambda: deque(maxlenmax_length)) self.ages defaultdict(int) self.max_age max_age self.colors {} def update(self, ids, boxes): current_ids set() for tid, box in zip(ids, boxes): current_ids.add(tid) cx int((box[0] box[2]) / 2) cy int((box[1] box[3]) / 2) self.trajectories[tid].append((cx, cy)) self.ages[tid] 0 if tid not in self.colors: self.colors[tid] self._random_color() for tid in list(self.trajectories.keys()): if tid not in current_ids: self.ages[tid] 1 if self.ages[tid] self.max_age: del self.trajectories[tid] del self.ages[tid] if tid in self.colors: del self.colors[tid] def get_trajectories(self): return self.trajectories, self.colors def _random_color(self): import random return (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255))max_length60表示每條軌跡最多保留60個點按30FPS算就是2秒的軌跡長度畫出來不會太亂。max_age30表示軌跡丟失30幀后刪除避免已經(jīng)離開畫面的車輛還留著軌跡線。顏色用隨機生成的方式每個ID固定一個顏色這樣看起來比較清晰。這里有個坑要注意deque的maxlen參數(shù)會讓舊數(shù)據(jù)自動彈出但如果你需要做軌跡回放或者速度計算就不能用deque得用普通list然后手動管理長度。我一開始用deque做速度估算發(fā)現(xiàn)歷史點被彈掉了算不準后來改成list加手動裁剪。3.4 界面布局與交互設計PyQt5的界面我用的是左右分欄布局左邊是視頻顯示區(qū)域占大概70%的寬度右邊是控制面板放視頻源選擇、模型選擇、參數(shù)調(diào)節(jié)、數(shù)據(jù)導出這些功能。視頻顯示區(qū)域用一個QLabel來承載每幀檢測結(jié)果通過cv2.cvtColor轉(zhuǎn)成RGB再用QImage和QPixmap顯示出來。這里有個性能優(yōu)化的點不要每幀都創(chuàng)建新的QImage對象可以復用同一個緩沖區(qū)。我實測下來復用緩沖區(qū)能減少大概15%的CPU占用。控制面板里比較重要的控件包括視頻文件選擇按鈕、攝像頭開關(guān)、置信度滑塊、IOU滑塊、軌跡長度滑塊、開始/暫停按鈕、導出CSV按鈕。置信度和IOU滑塊實時綁定到檢測參數(shù)上用戶拖動就能看到效果變化這個交互體驗比改配置文件好太多。提示PyQt5的滑塊控件QSlider默認是整數(shù)如果你需要0.1精度的參數(shù)可以把范圍設成0到100用的時候除以100。3.5 數(shù)據(jù)記錄與導出模塊交通分析軟件的一個核心價值是數(shù)據(jù)導出。我實現(xiàn)了兩個導出功能檢測結(jié)果CSV和軌跡數(shù)據(jù)CSV。檢測結(jié)果CSV每幀記錄一行幀號、時間戳、目標ID、類別、置信度、左上角坐標、右下角坐標。軌跡數(shù)據(jù)CSV記錄每個ID的完整軌跡點序列。導出用Python內(nèi)置的csv模塊就行不需要額外依賴。import csv from datetime import datetime class DataExporter: def __init__(self): self.detection_records [] self.trajectory_records {} def add_detection(self, frame_id, timestamp, tid, cls_name, conf, box): self.detection_records.append({ frame: frame_id, timestamp: timestamp, track_id: tid, class: cls_name, confidence: round(conf, 3), x1: box[0], y1: box[1], x2: box[2], y2: box[3] }) def export_detections(self, filepath): if not self.detection_records: return False keys self.detection_records[0].keys() with open(filepath, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnameskeys) writer.writeheader() writer.writerows(self.detection_records) return Trueencodingutf-8-sig這個細節(jié)很重要不加的話用Excel打開中文會亂碼。newline是為了避免Windows下出現(xiàn)空行。4. 實操過程中的關(guān)鍵環(huán)節(jié)4.1 環(huán)境搭建與依賴安裝環(huán)境這塊我踩過不少坑這里直接給出一套驗證過的方案。Python版本建議3.9到3.11太新的版本有些庫還沒適配。CUDA版本根據(jù)你的顯卡來30系顯卡用CUDA 11.840系用CUDA 12.1。conda create -n traffic_yolo python3.10 conda activate traffic_yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install PyQt5 opencv-python pandas安裝順序有講究先裝PyTorch再裝ultralytics因為ultralytics會檢查torch版本如果先裝ultralytics它會自動拉一個CPU版本的torch后面再換GPU版本很麻煩。opencv-python不要裝opencv-python-headless因為我們需要cv2.imshow之外的GUI功能headless版本缺少一些窗口相關(guān)的函數(shù)。驗證安裝是否成功import torch print(torch.cuda.is_available()) # 應該輸出True from ultralytics import YOLO model YOLO(yolov8n.pt) print(model.names) # 應該輸出COCO的80個類別如果torch.cuda.is_available()返回False檢查CUDA版本和顯卡驅(qū)動是否匹配。我遇到過驅(qū)動太舊導致CUDA不可用的情況更新驅(qū)動就好了。4.2 模型下載與自定義訓練YOLOv8的預訓練模型在第一次調(diào)用YOLO(yolov8n.pt)時會自動下載但國內(nèi)網(wǎng)絡有時候會卡住。你可以手動從Ultralytics的GitHub Release頁面下載放到當前目錄下代碼里直接寫文件名就行。如果你要做特定場景的檢測比如只檢測某種特殊車輛就需要自定義訓練。數(shù)據(jù)標注用LabelImg或者Roboflow導出YOLO格式的txt標注文件。訓練命令很簡單yolo train modelyolov8n.pt datatraffic.yaml epochs100 imgsz640 batch16traffic.yaml里配置訓練集和驗證集的路徑、類別數(shù)量和類別名稱。我建議至少準備2000張以上的標注圖片每個類別至少500個實例否則模型容易過擬合。訓練100輪大概需要2到4小時取決于顯卡性能。注意自定義訓練時推理代碼里的classes參數(shù)要改成你自己數(shù)據(jù)集的類別索引不要照搬COCO的2、3、5、7。4.3 視頻源接入與幀率控制視頻源支持三種本地視頻文件、USB攝像頭、RTSP網(wǎng)絡流。本地文件用cv2.VideoCapture(path)攝像頭用cv2.VideoCapture(0)RTSP流用cv2.VideoCapture(rtsp://...)。幀率控制是個容易被忽略的點。如果推理速度比視頻幀率高不加控制的話視頻會飛快播完。我的做法是記錄每幀的處理時間如果處理太快就sleep補足到目標幀率import time target_fps 30 frame_time 1.0 / target_fps while running: start time.time() ret, frame cap.read() # ... 推理和繪制 ... elapsed time.time() - start if elapsed frame_time: time.sleep(frame_time - elapsed)如果推理速度比目標幀率慢那就沒辦法了只能跳幀或者降低分辨率。我一般會把輸入分辨率從1080p降到720p推理速度能提升差不多一倍精度損失在可接受范圍內(nèi)。4.4 軌跡繪制的美化處理軌跡繪制不是簡單地把點連起來就行有幾個美化技巧能讓效果專業(yè)很多。第一軌跡線用漸變色或者透明度漸變。越新的點越亮越舊的點越暗這樣能直觀看出車輛的運動方向。實現(xiàn)方式是給每個點設置不同的alpha值但OpenCV的polylines不支持透明度需要先畫到一個透明圖層上再疊加。第二軌跡線寬度隨速度變化。速度快的時候線粗一點速度慢的時候線細一點視覺上更有動感。速度可以通過相鄰兩幀的中心點距離除以時間間隔來估算。第三檢測框的樣式。我用的是圓角矩形加半透明填充比單純的矩形框好看很多。圓角用cv2.ellipse畫四個角直線用cv2.line連接。半透明填充需要cv2.addWeighted疊加。def draw_rounded_rect(img, pt1, pt2, color, thickness2, r8): x1, y1 pt1 x2, y2 pt2 cv2.line(img, (x1r, y1), (x2-r, y1), color, thickness) cv2.line(img, (x1r, y2), (x2-r, y2), color, thickness) cv2.line(img, (x1, y1r), (x1, y2-r), color, thickness) cv2.line(img, (x2, y1r), (x2, y2-r), color, thickness) cv2.ellipse(img, (x1r, y1r), (r, r), 180, 0, 90, color, thickness) cv2.ellipse(img, (x2-r, y1r), (r, r), 270, 0, 90, color, thickness) cv2.ellipse(img, (x1r, y2-r), (r, r), 90, 0, 90, color, thickness) cv2.ellipse(img, (x2-r, y2-r), (r, r), 0, 0, 90, color, thickness)4.5 多線程與界面響應優(yōu)化PyQt5的多線程有個鐵律所有界面更新必須在主線程做。子線程里直接操作控件會崩潰或者出現(xiàn)奇怪的問題。我的做法是子線程只發(fā)信號主線程收到信號后更新界面。信號傳遞的數(shù)據(jù)類型要注意PyQt5的信號支持pyqtSignal(np.ndarray, list, list)這種多參數(shù)形式但numpy數(shù)組需要確保是連續(xù)的。如果遇到信號傳遞失敗的情況檢查一下數(shù)組是不是被切片成了非連續(xù)內(nèi)存。還有一個性能優(yōu)化的點界面刷新不需要每幀都做。如果推理是30FPS界面可以降到15FPS刷新人眼看起來依然流暢但CPU占用能降不少。實現(xiàn)方式是設置一個計數(shù)器每兩幀才更新一次界面。5. 常見問題與排查實錄5.1 檢測框閃爍和ID跳變這是最常見的問題表現(xiàn)為同一輛車在相鄰幀里ID突然變了或者檢測框忽大忽小。原因通常有三個檢測置信度閾值設得太低導致誤檢、ByteTrack參數(shù)不匹配、視頻幀間差異太大。排查步驟先把conf閾值提高到0.6以上看ID跳變是否減少。如果還不行檢查track_high_thresh和new_track_thresh適當提高。如果視頻本身抖動嚴重可以考慮加一個簡單的幀間平滑或者用光流做預處理。我遇到過一次特別詭異的情況同一輛車在畫面中間ID很穩(wěn)定一到畫面邊緣就跳變。后來發(fā)現(xiàn)是邊緣的車輛被截斷檢測框不完整導致匹配失敗。解決辦法是在軌跡管理里對邊緣區(qū)域的檢測框做特殊處理或者干脆忽略畫面邊緣10%區(qū)域內(nèi)的目標。5.2 推理速度慢導致界面卡頓如果推理速度跟不上視頻幀率界面會明顯卡頓。先確認是不是在用GPU推理torch.cuda.is_available()返回False的話就是CPU在跑速度會慢10倍以上。如果GPU確認沒問題但速度還是慢檢查輸入分辨率。YOLOv8默認640x640如果你傳的是1080p原圖模型內(nèi)部會先縮放這個縮放過程有開銷??梢允謩影褞s放到640再傳入減少不必要的計算。還有一個容易被忽略的點model.track()每次調(diào)用都會做NMS后處理如果檢測框很多NMS會很耗時??梢酝ㄟ^設置max_det參數(shù)限制每幀最大檢測數(shù)交通場景一般50個就夠了。5.3 軌跡線拖尾和殘留軌跡線拖尾是指車輛已經(jīng)離開畫面了軌跡線還留在那里。這是max_age設得太大導致的。max_age30意味著軌跡丟失后還要保留30幀按30FPS算就是1秒。如果你覺得拖尾太長把max_age降到15或20。殘留問題是指軌跡線畫在了錯誤的位置。這通常是坐標系轉(zhuǎn)換的問題。OpenCV的坐標原點在左上角PyQt的坐標原點也在左上角但如果你做了縮放顯示坐標就要同步縮放。我的做法是在繪制之前把所有坐標統(tǒng)一轉(zhuǎn)換到顯示分辨率下避免混用。5.4 內(nèi)存泄漏與長時間運行崩潰長時間運行后內(nèi)存持續(xù)增長最后崩潰這是桌面軟件常見的問題。原因通常是信號槽連接沒有斷開、QImage對象沒有釋放、或者軌跡字典無限增長。排查方法用tracemalloc或者memory_profiler監(jiān)控內(nèi)存變化找到增長最快的對象。我遇到過一次是QImage對象在每次刷新時都新建舊對象沒有被GC回收。解決辦法是復用QImage緩沖區(qū)或者手動調(diào)用del釋放。軌跡字典的增長也要控制。即使有max_age清理如果視頻里車輛特別多字典還是會很大。我加了一個上限比如最多保留500條軌跡超過就清理最舊的。5.5 常見問題速查表問題現(xiàn)象可能原因排查方法解決方案檢測框閃爍置信度閾值過低提高conf到0.6調(diào)整conf和iou參數(shù)ID頻繁跳變ByteTrack參數(shù)不匹配觀察ID Switch次數(shù)提高track_high_thresh界面卡頓推理在CPU上跑檢查cuda.is_available()安裝GPU版本PyTorch軌跡拖尾max_age過大觀察軌跡消失延遲降低max_age到15-20內(nèi)存持續(xù)增長對象未釋放用memory_profiler監(jiān)控復用緩沖區(qū)限制字典大小視頻播放過快未做幀率控制檢查是否有sleep加幀率同步邏輯導出CSV亂碼編碼問題用記事本打開檢查用utf-8-sig編碼攝像頭打不開被其他程序占用檢查設備管理器關(guān)閉其他占用程序提示這張表是我在實際調(diào)試中總結(jié)出來的大部分問題都能在這里找到對應方案。如果遇到表里沒有的問題優(yōu)先檢查版本兼容性ultralytics和PyQt5的版本更新比較頻繁有時候降級一個版本就好了。6. 一些實操心得和擴展思路這個軟件做完之后我用了大概兩個月跑過各種場景的視頻包括高速公路、城市路口、停車場、夜間監(jiān)控。有幾個心得值得分享。夜間場景的檢測效果明顯下降這是YOLOv8的固有局限訓練數(shù)據(jù)里夜間樣本少。如果你的應用場景主要是夜間建議收集夜間數(shù)據(jù)做微調(diào)訓練或者加一個圖像增強預處理比如直方圖均衡化或者CLAHE能提升一些效果。密集車流場景下ByteTrack的ID Switch會增多因為車輛之間遮擋嚴重。這種情況可以考慮換成OC-SORT它對遮擋的處理更好但速度會慢一些。或者用BoT-SORT它結(jié)合了外觀特征和運動信息效果最好但需要額外的ReID模型。軌跡分析這塊還可以擴展很多功能。比如車輛速度估算通過軌跡點的像素距離和幀率換算成實際速度需要標定像素和實際距離的比例。再比如車輛變道檢測通過軌跡的橫向偏移量來判斷。還有流量統(tǒng)計在畫面里畫一條虛擬線統(tǒng)計穿越線的車輛數(shù)量。導出數(shù)據(jù)這塊也可以做得更豐富。除了CSV還可以導出JSON格式方便程序讀取或者直接生成HTML報告里面嵌入軌跡圖和統(tǒng)計圖表。我用matplotlib生成過軌跡熱力圖能直觀看出車輛的主要行駛路徑對交通分析很有價值。最后說一個部署上的經(jīng)驗。如果你要把這個軟件交給完全不懂技術(shù)的用戶最好用PyInstaller打包成exe。打包的時候注意把模型文件、配置文件、圖標資源都包含進去用--add-data參數(shù)指定。打包后的exe體積會比較大因為PyTorch和OpenCV的庫很占空間但用戶不需要裝任何環(huán)境雙擊就能用體驗好很多。