1.項目介紹
本文檔是對于“車流量檢測平臺”的應用技術進行匯總,適用于此系統所有開發,測試以及使用人員,其中包括設計背景,應用場景,系統架構,技術分析,系統調度,環境依賴,以及運行指導,其中技術分析為主要部分,包括UI設計,視頻輸入輸出,圖像預處理,數據提取,處理及預測等。
2.設計背景
政治角度:
隨著城市化進程的加快和交通擁堵問題的日益嚴重,政府對于智能交通系統的建設與管理給予了高度重視。基于視頻的車流量檢測平臺作為智能交通系統的重要組成部分,其開發與應用有助于提升城市治理水平,滿足政府對交通狀況實時監測與管理的需求。
經濟角度:
隨著信息技術的快速發展,視頻處理技術、計算機視覺和深度學習等領域取得了顯著進步,為基于視頻的車流量檢測平臺的開發提供了技術支撐。同時,隨著汽車保有量的不斷增加,交通管理領域對于智能化、自動化的需求也日益旺盛,推動了基于視頻的車流量檢測平臺的研發與應用。
社會角度:
公眾對于出行效率、交通安全和環境質量的關注度不斷提高,對于智能交通系統的期待也日益增強。基于視頻的車流量檢測平臺能夠實時監測交通狀況,為公眾提供更加便捷、安全的出行環境,同時也有助于減少交通擁堵和環境污染,提升城市生活質量。
此外,傳統的車流量檢測方法主要依賴于傳感器設備,如地磁傳感器、紅外線傳感器等。這些傳感器需要在道路上布設,成本較高且安裝維護困難。而基于計算機視覺的車流量檢測系統則可以通過分析道路上的攝像頭圖像來實現,具有成本低、安裝方便、實時性強等優勢。因此,設計一種高效、準確且易于實現的汽車流量檢測系統顯得尤為重要。
3.場景應用
隨著城市化進程的加快和交通工具的普及,車輛數量的快速增長給城市交通管理帶來了巨大的挑戰。因此,交通流量檢測成為了交通管理的重要組成部分。它能夠提供實時的交通狀況信息,幫助交通管理部門制定合理的交通策略,優化交通流量,提高道路利用效率,減少交通擁堵和事故發生的可能性。
基于視頻的車流量檢測平臺可廣泛應用于城市道路、高速公路、橋梁隧道等交通場景中。通過高清攝像頭實時拍攝車輛圖像,利用圖像處理算法對圖像進行分析和處理,提取出車輛的位置、數量和運動軌跡等信息。這些信息可用于交通管理部門對道路交通情況的監測和分析,也可用于交通規劃和設計、交通預測和智能交通系統等領域。
解決的社會痛點
1. 交通擁堵問題:
基于視頻的車流量檢測平臺能夠實時監測交通流量和車輛速度等信息,為交通管理部門提供實時、準確的數據支持。通過數據分析,可以及時發現交通擁堵點,采取相應的交通控制措施,優化交通流動,減少交通擁堵現象。
2. 交通安全問題:
通過對車輛運動軌跡的監測和分析,平臺可以及時發現異常行駛行為,如超速、逆行等,為交通管理部門提供預警信息,有助于減少交通事故的發生。
3. 環境質量問題:
平臺還可以監測城市環境質量,如空氣質量、噪聲等。通過對這些數據的分析,可以及時發現環境質量不佳的區域,采取相應措施減少污染源,提高城市環境質量。
4. 公共服務效率問題:
在公共交通領域,基于視頻的車流量檢測平臺可以實時監測公交、地鐵等公共交通的運行情況,為公眾提供更加精準的出行信息,提高公共服務的效率和質量。
4.系統架構
4.1用戶界面層
使用QT框架開發圖形用戶界面(GUI),展示實時視頻流、預處理視頻流,流量統計數據、當天車流信息以及未來車流信息等。用戶可以通過此界面進行參數設置、啟動/停止檢測等操作。
4.2視頻處理層
接入攝像頭或視頻流,利用OpenCV庫進行視頻捕獲和預處理。實現車輛檢測算法,包括提取物體掩碼、運動目標檢測、車輛識別等步驟。將檢測到的車輛信息傳遞給下一層。
4.3數據處理與存儲層
接收視頻處理層傳遞的車輛信息,并進行進一步的分析和處理,包括提取真實汽車掩碼,以及Opencv格式的圖片與Qt支持的圖片進行格式轉化。將可用數據提取出來并進一步的過濾,并且利用梯度下降法擬合數據的函數模型。
4.5數據存儲層
將處理后的數據存儲在數據庫中,包括車輛流量統計數據、時間戳。實現數據查詢,預測和報表生成功能,供用戶層調用和展示。
5.技術分析
1視頻處理流程圖
2 UI設計圖
客戶端界面利用QT5搭建而成,這些窗口分別是源視頻識別并標記顯示窗口,計算機圖像學處理后的窗口,未來數據預期窗口,用來顯示未來某天二十四小時的車流量,當天的車流量情況展示窗口,最后一個是日歷,可以選擇日期,還有對于視頻流的播放控制有兩個按鈕控制。
使用Qlabel控件,在其之上利用繪圖事件,分別將標記后的視頻流和預處理視頻流播放出來。
折線統計圖用于展示以前某天或未來某天24小時的車流量變化情況,條形統計圖用于實時展示當天的車流情況。
中間兩個窗口分別顯示實時監控的視頻圖像以及汽車輪廓和視頻流處理過程。
利用QToolButton控件設置兩個按鈕來控制視頻流的播放。
兩個QEditLine 控件用于輸入機器學習的頻率和次數。
那個大的按鈕是主動學習的控制開關,點擊觸發后變為紅色,開始學習,并再右上角的編輯框顯示學習進度。
使用日歷控件可以先擇事件,如果選擇過去的某天,就將過去的某天二十四小時的車流量情況圖像化的顯示,如果選擇未來某天,就將這天預測的數據顯示出來。
在C++中,使用OpenCV庫處理視頻流時,cv::VideoCapture?類是一個非常重要的組件,它用于捕獲視頻流,可以從攝像頭、視頻文件或IP攝像頭流中讀取幀。
使用實例
cv::VideoCapture cap(0); // 檢查攝像頭是否成功打開 if (!cap.isOpened()) { std::cerr << "Error opening video stream or file" << std::endl; return -1; } cv::namedWindow("Video", cv::WINDOW_AUTOSIZE); while (true) { // 讀取一幀圖像 cap >> frame; // 檢查是否成功讀取幀 ???if (frame.empty()) { std::cout << "Can't receive frame (stream end?). Exiting ..." << std::endl; break; } cv::imshow(”Video”,res); } |
3 最終呈現圖
圖片前景掩碼(通常簡稱為前景掩碼)在圖像處理中是一個重要的概念,主要用于從圖片中分離出前景元素。掩碼是一個二值圖像,其中前景元素被標記為白色(或其他高亮顏色),而背景元素則被標記為黑色(或其他暗色)。通過這種方式,掩碼可以幫助我們在后續的圖像處理過程中只處理或操作前景元素,而忽略背景元素。基于顏色或亮度的差異:如果前景和背景在顏色或亮度上有明顯的差異,可以使用閾值操作或色彩分割等方法來生成掩碼。一旦生成了前景掩碼,就可以將其應用于各種圖像處理任務,如前景元素的提取、背景的替換、圖像合成等。
4 汽車前景掩碼圖
接口解釋:
setShadowThreshold()
這個方法是用來設置陰影檢測閾值的。陰影閾值決定了算法如何區分前景對象和其可能產生的陰影。通過調整這個閾值,你可以影響算法對陰影的敏感度。
apply()
apply()方法是BackgroundSubtractorMOG2類的主要方法,用于執行背景減除。當你調用這個方法時,它會處理傳入的圖像,并返回一個新的圖像,其中前景對象(即移動的對象)被高亮顯示(通常是白色),而背景則被抑制(通常是黑色)。
二值圖像中一類主要處理是對提取的目標圖形進行形態分析。形態學處理中最基本的是腐蝕和膨脹。腐蝕和膨脹是兩個互為對偶的運算。腐蝕的作用是將目標圖像收縮,而膨脹是將圖像擴大。結構元素是指具有某種確定形狀的基本結構元素,例如,一定大小的矩形、圓形等。結構元素具有原點。
5.3.2.1 形態學
形態學操作是根據圖像形狀進行的簡單操作。一般情況下對二值化圖像/灰度圖像進行操作。它需要輸入兩個操作,一個是原始圖像,另一個被稱為結構化元素或核,它是用來決定操作的性質的。兩個基本的形態學操作是腐蝕和膨脹,它們的變體構成了開運算、閉運算和梯度等。
5 腐蝕/膨脹對比圖
5.3.2.2 腐蝕
腐蝕的效果是把圖片"變瘦",其原理是在原圖的小區域內取局部最小值。因為是二值化圖,只有 0 和 255,所以小區域內有一個是 0 該像素點就為 0:
5.3.2.3 膨脹
膨脹與腐蝕相反,取的是局部最大值,效果是把圖片"變胖"。
5.3.2.4 開/閉運算
開運算:先腐蝕后膨脹,可用以消除黑色背景中的白點雜質。
閉運算:先膨脹后腐蝕,可用以消除白色前景中的黑點雜質。
6 形態學閉運算處理圖
接口:
腐蝕(Erosion)
void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), intiterations=1,IntborderType=BORDER_CONSTANT,
const?Scalar&borderValue=morphologyDefaultBorderValue());
參數解釋:
src:輸入圖像,通常是二值圖像。
dst:輸出圖像,與輸入圖像具有相同的尺寸和類型。
kernel:用于腐蝕的結構元素,定義了鄰域的形狀和大小。
anchor:結構元素的錨點位置。默認值是(-1, -1),表示錨點位于結構元素的中心。
iterations:腐蝕操作的迭代次數。
borderType:像素外推法的類型。
borderValue:使用邊界類型BORDER_CONSTANT時的邊界值。
膨脹(Dilation)
void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1),
int iterations=1,int?borderType=BORDER_CONSTANT,
?const Scalar& borderValue=morphologyDefaultBorderValue());
參數解釋:
src:輸入圖像,通常是二值圖像。
dst:輸出圖像,與輸入圖像具有相同的尺寸和類型。
kernel:用于膨脹的結構元素,定義了鄰域的形狀和大小。
anchor:結構元素的錨點位置。默認值是(-1, -1),表示錨點位于結構元素的中心。
iterations:膨脹操作的迭代次數。
borderType:像素外推法的類型。
borderValue:使用邊界類型BORDER_CONSTANT時的邊界值。
高斯濾波(Gauss Filter)是線性濾波中的一種。在OpenCV圖像濾波處理中,高斯濾波用于平滑圖像,或者說是圖像模糊處理,因此高斯濾波是低通的。其廣泛的應用在圖像處理的減噪過程中,尤其是被高斯噪聲所污染的圖像上。
高斯濾波的基本思想是: 圖像上的每一個像素點的值,都由其本身和鄰域內其他像素點的值經過加權平均后得到。其具體操作是,用一個核(又稱為卷積核、掩模、矩陣)掃描圖像中每一個像素點,將鄰域內各個像素值與對應位置的權值相稱并求和。從數學的角度來看,高斯濾波的過程是圖像與高斯正態分布做卷積操作。
注意: 高斯濾波是將二維高斯正態分布放在圖像矩陣上做卷積運算。考慮的是鄰域內像素值的空間距離關系,因此對彩色圖像處理時應分通道進行操作,也就是說操作的圖像原矩陣時用單通道數據,最后合并為彩色圖像。
5.3.3.1 一維高斯函數
可以看到,G(x)的跟sigma的取值有極大的關系。sigma取值越大,圖像越平緩,sigma取值越小,圖像越尖銳。
7 一維高斯函數
5.3.3.2 二維高斯函數
二維高斯是構建高斯濾波器的基礎。可以看到,G(x,y)在x軸y軸上的分布是一個突起的帽子的形狀。這里的sigma可以看作兩個值,一個是x軸上的分量sigmaX,另一個是y軸上的分量sigmaY。對圖像處理可以直接使用sigma并對圖像的行列操作,也可以用sigmaX對圖像的行操作,再用sigmaY對圖像的列操作。它們是等價的。當sigmaX和sigmaY取值越大,整個形狀趨近于扁平;當sigmaX和sigmaY取值越小,整個形狀越突起。
高斯濾波原理就是將上圖的二維正態分布應用在二維的矩陣上,G(x,y)的值就是矩陣上的權值,將得到的權值進行歸一化,將權值的范圍約束在[0,1]之間,并且所有的值的總和為1。可以看到,權值的分布是以中間高四周低來分布的。并且距離中心越遠,其對中心點的影響就越小,權值也就越小。
因此可以總結:
(1)在核大小固定的情況下,sigma值越大,權值分布越平緩。因此,鄰域各個點的值對輸出值的影響越大,最終結果造成圖像越模糊。
(2)在核大小固定的情況下,sigma值越小,權值分布越突起。因此,鄰域各個點的值對輸出值的影響越小,圖像變化也越小。假如中心點權值為1,其他點權值為0,那么最終結果是圖像沒有任何變化。
(3)sigma固定時,核越大圖像越模糊。
(4)sigma固定時,核越小圖像變化越小。
9 采用高斯濾波后的圖
接口解釋:
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT);
參數解釋:
src:輸入圖像,可以是多通道圖像。
dst:輸出圖像,與輸入圖像具有相同的尺寸和類型。
ksize:高斯核的大小。它必須是正奇數。
sigmaX:表示高斯核函數在X方向的標準差。
sigmaY:表示高斯核函數在Y方向的標準差。如果sigmaY設置為0,那么它會被設置為與sigmaX相同的值。兩個sigma值決定了高斯核的形狀。
borderType:像素外推法的類型,決定了當高斯核應用到圖像邊界時如何處理邊界外的像素。默認值是BORDER_DEFAULT。
原圖通過一系列圖形學處理后,已經可以提取出想要的數據了,接下來就是數據數據除雜和過濾的過程。
5.4.1.1提取汽車掩碼
尋找所有汽車掩碼前景的連通圖,如果連通圖的長寬,以及面積都復合實驗閾值,那么此掩碼就是汽車的掩碼。
10 計算汽車前景掩碼流程圖
核心算法
?findContours(dst, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); ????for (size_t i = 0; i < contours.size(); i++) ????{ ????????Rect2d re = c2d(boundingRect(contours[i])); ????????if (re.width>90&& re.width<440 &&re.height>140&& re.height<440) ????????{ ????????????box3.push_back(re); ????????????rectangle(frame, re, cv::Scalar(0, 255, 0), 2); ????????} ????} |
5.4.1.2 數據過濾
依次和前一次的連通圖組逐個進行比較,如果出現重疊面積大于等于當前面積的百分之十,則認為此汽車已經計數,否則依次向更久的連通組比較,直到遍歷完所有歷史數據。最終用所有車減去歷史出現的車等于新出現的車。
11 統計汽車數量流程圖
核心算法
int Run::findsum() { ????int sum = 0; ????for (size_t i = 0; i < box3.size(); i++) ????{ ????????bool bo = false; ????????for (size_t j = 0; j < box2.size(); j++) ????????{ ????????????if (overlapArea(box3[i], box2[j]) / box3[i].area()> 0.1) ????????????{ ????????????????bo = true; ????????????????break; ????????????} ????????????else ????????????{ ????????????????for (size_t k = 0; k < box1.size(); k++) ????????????????{ ????????????????????if (overlapArea(box3[i], box1[k]) / box3[i].area()> 0.1) ????????????????????{ ????????????????????????bo = true; ????????????????????????break; ????????????????????} ????????????????} ????????????} ????????} ????????if (bo)sum++; ????} ????return sum; } |
如圖所示,利用連續三幀圖像來確定是否有新出現的車輛,這三幀中總共出現了七輛車。
流量檢測過程圖:
12-1 第一幀有四輛車
12-2 第二幀有兩輛車,其中新出現了一輛
12-3 第三幀有四輛車,其中新出現了量輛
利用當幀圖像中汽車的總數量-前一幀汽車的數量=當前幀新出現的汽車數量。
而其中計算是否為之前已經出現過的車利用了面積重疊法。
13 判斷是否為已經掃描過的車輛示意圖
核心算法
double Run::overlapArea(Rect2d rect1, Rect2d rect2) { ????int overlapLeft = std::max(rect1. x, rect2.x); ????int overlapRight = std::min(rect1.x+rect1.width, rect2.x+rect2.width); ????int overlapTop = std::max(rect1.y, rect2.y); ????int overlapBottom = std::min(rect1.y+rect1.height, rect2.y+rect2.height); ????// 如果重疊區域的左右或上下邊界無效(即沒有重疊),則返回0 ????if (overlapLeft >= overlapRight || overlapTop >= overlapBottom) ????{ ????????return 0; ????} ????// 計算重疊區域的寬度和高度 ????int overlapWidth = overlapRight - overlapLeft; ????int overlapHeight = overlapBottom - overlapTop; ????// 計算重疊面積 ????return overlapWidth * overlapHeight; } |
數據存儲利用關系型數據SQLite。SQLite是一個功能強大且易于使用的數據庫系統,特別適用于那些需要輕量級、嵌入式和跨平臺數據庫解決方案的場景。這個表tim字段是一個INTEGER類型,可轉為long long,用于存儲UNIX時間戳。num字段是一個整型,用于存儲車輛數量。可以從數據庫中拿到歷史數據,也可以將新掃描的數據存放到數據庫中。
建表語句
CREATE?TABKE?car_table ( tim INTEGER PRIMARY KEY, num INT NOT NULL); |
我們首先想到簡單線性回歸分析,但是通過觀察發現車流量變化的函數曲線接近于正弦曲線,因此采用梯度下降擬合曲線。
使用梯度下降法來擬合y = a * sin(b * X + c) + d形式的模型是一個迭代的過程,其中我們逐步調整模型的參數a,?b,?c, d,以最小化預測值和實際值之間的誤差。
5.4.5.1梯度下降
14梯度下降求極值示意圖
5.4.5.1.1算法簡介
梯度下降法(Gradient Descent)是一種優化算法,常用于機器學習和深度學習的訓練過程中,特別是用于求解損失函數的最小值。該算法的基本思想是通過迭代的方式調整模型參數,以使得損失函數逐漸減小,從而逼近最小值。
梯度下降法的工作原理如下:
1.選擇初始參數:首先,需要為模型的參數選擇一個初始值。這些參數可以是隨機選擇的,也可以是基于某種啟發式方法選擇的。
2.計算梯度:在每一次迭代中,計算損失函數關于模型參數的梯度。梯度表示了損失函數在各個方向上的變化率,因此指向了損失函數減小最快的方向。
3.更新參數:根據計算得到的梯度,按照一定的學習率(learning rate)來更新模型的參數。學習率是一個超參數,它決定了每次參數更新的步長。過大的學習率可能導致算法不穩定,而過小的學習率則可能導致算法收斂速度過慢。
4.重復迭代:重復執行步驟2和步驟3,直到滿足某種停止條件,如損失函數值小于某個閾值,或者達到預設的最大迭代次數。
1.為什么按負梯度下降
《數學分析》中,負梯度方向是函數下降最快的方向,即x=[2,3],如果梯度為[1,2],則x往[1,2]方向調整,能令函數f(x)下降最快的方向(所謂最快,即調整同樣的步長,該方向能令函數下降最快)按負梯度下降,保證了調整方向的正確性。
2.為什么要設置學習率
目的是為了保證按梯度方向調整一定能下降。梯度方向能下降是瞬時的,如果調整步長過大,則不一定能保證函數能下降,但只要調整步長足夠小,函數就能下降(前提是梯度不為0)。所以,我們在調整時,加入學習率lr,以控制步長:
3、學習率的設置與自適應學習率
要保證能下降,學習率就不能過大,但學習率很小,每次迭代調整都很小,就需要迭代很多次。為此,我們可以設定一個較中肯的學習率(例如,lr = 0.1)。如果更智能一些,在程序中把學習率改為自適應學習率: 函數能下降,我們把學習率調大些,如果函數本次迭代不能下降,我們就把學習率調小些。
5.4.5.1.2初始化參數
A = -100.0; // 振幅初始估計 B = 2.0 * std::acos(-1.0) / (7680/2); // 頻率初始估計,假設周期為12h C = 0.0; // 相位初始估計 D = 150.0; // 偏移量初始估計 |
5.4.5.1.3正弦函數擬合模型
double myData::sine_model(long long x, double A, double B, double C, double D) { return A * std::sin(B * x + C) + D; } |
5.4.5.1.4計算誤差平方和
double myData:: compute_error(const std::vector<long long>& x_data, const std::vector<int>& y_data, double A, double B, double C, double D) { ????double error = 0.0; ????for (size_t i = 0; i < x_data.size(); ++i) ????{ ????????double y_pred = sine_model(x_data[i], A, B, C, D); ????????error += std::pow(y_data[i] - y_pred, 2); ????} ????return error; } |
5.4.5.1.5梯度下降法優化參數
void myData::gradient_descent(const std::vector<long long>& x_data, const std::vector<int>& y_data,double& A, double& B, double& C, double& D,double learning_rate, int iterations) { for (int iter = 0; iter < iterations; ++iter) { ???double dA = 0.0, dD = 0.0; ???double dB = 0.0, dC = 0.0; ???//double error = compute_error(x_data, y_data, A, B, C, D); ???// 計算梯度 ???for (size_t i = 0; i < x_data.size(); ++i) ???{ ??????long long x = x_data[i]; ??????double y = y_data[i]; ??????//qDebug()<<x<<"---------"<<y; ??????double y_pred = sine_model(x,A,B,C,D); ??????????dA += ?(y - y_pred) * std::sin(B * x + C); ??????dB += ?(y - y_pred) * A * x * std::cos(B * x + C); ??????dC += ?(y - y_pred) * A * std::cos(B * x + C); ??????dD += ?(y - y_pred); ???} ???// 更新參數 ???A -= learning_rate * dA / x_data.size(); ???B?-= learning_rate * dB / x_data.size(); ???C -= learning_rate * dC / x_data.size(); ???D-= learning_rate * dD / x_data.size(); ??// 輸出迭代信息(可選) ??if (iter % 100 == 0) ??{ ????qDebug() << "Iteration " << iter << ": A=" << A << ", B=" << B << ", C=" << C << ", D=" << D<< "; ??} } } |
5.4.5.2線性回歸
線性回歸是一種統計學上分析數據的方法,用來確定兩種或兩種以上變量之間關系的強度和方向。它通常用于預測一個因變量(或響應變量)基于一個或多個自變量(或預測變量)的變化。這種方法得名于它使用一個或多個獨立變量(或特征)的線性組合來預測因變量(或目標變量)。
通過將時間轉化為時間戳:X;汽車流量:Y之后就可以進行線性回歸分析,Y=K*X+b;
通過最小二乘法計算線性回歸的系數:
pair<double,double>calculateLinearRegression(vector<double>& x, vector<double>& y) ?{ ????int n = x.size(); ? ????// 計算x和y的均值 ? ????double mean_x = accumulate(x.begin(), x.end(), 0.0) / n; ? ????double mean_y = accumulate(y.begin(), y.end(), 0.0) / n; ? ????// 計算x和y的乘積之和,以及x的平方和 ? ????double sum_xy = 0.0; ? ????double sum_x2 = 0.0; ? ????for (int i = 0; i < n; ++i) { ? ????????sum_xy += x[i] * y[i]; ? ????????sum_x2 += pow(x[i], 2); ? ????} ??? ????// 計算斜率m和截距b ? double m = (n * sum_xy - accumulate(x.begin(), x.end(), 0.0) * accumulate(y.begin(), y.end(), 0.0)) /(n * sum_x2 - pow(accumulate(x.begin(), x.end(), 0.0), 2)); ? ????double b = mean_y - m * mean_x; ??? ????return {m, b}; ? } ? |
將?OpenCV 的圖像格式轉換為 Qt 的圖像格式。函數檢查輸入的?cv::Mat?對象的類型。OpenCV 支持多種圖像類型,但此函數主要處理三種類型:CV_8UC1(單通道8位無符號整數,通常用于灰度圖像)、CV_8UC3(三通道8位無符號整數,通常用于彩色圖像)和?CV_8UC4(四通道8位無符號整數,通常用于帶有 alpha 通道的彩色圖像)。
QImage Run:: getQImage(const cv::Mat& mat) { ????if (mat.type() == CV_8UC1) ????{ ????????QImage image(mat.cols, mat.rows, QImage::Format_Indexed8); ????????image.setColorCount(256); ????????for (int i = 0; i < 256; i++) ????????{ ????????????image.setColor(i, qRgb(i, i, i)); ????????} ????????uchar* pSrc = mat.data; ????????for (int row = 0; row < mat.rows; row++) ????????{ ????????????uchar* pDest = image.scanLine(row); ????????????memcpy(pDest, pSrc, mat.cols); ????????????pSrc += mat.step; ????????} ????????return image; } else if (mat.type() == CV_8UC3) { ????const uchar* pSrc = (const uchar*)mat.data; ????????QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888); ????????return image.rgbSwapped(); ????} ????else if (mat.type() == CV_8UC4) { ????const uchar* pSrc = (const uchar*)mat.data; ????????QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32); ????????return image.copy(); ????} ????????return QImage(); } |
QT事件循環是Qt框架中處理事件的核心機制,它確保了應用程序能夠響應并處理各種異步事件,事件循環通過一個事件隊列來管理和調度事件。當隊列中有事件時,事件循環會從隊列中依次取出事件并處理,直到隊列為空或者事件循環被中斷。
QT信號槽是Qt框架中一種獨特且強大的對象間通信機制。信號(signal)是對象發出的一種特定事件,而槽(slot)則是用于響應這些信號的特定函數或方法。當某個對象發出信號時,與之相關聯的槽函數會被自動調用,從而實現了對象間的通信和協作。
信號和槽的連接可以是一對一、多對一、一對多或者多對多,這使得對象間的通信非常靈活。同時,信號和槽機制還具有解耦和靈活性的優點,因為對象之間通過信號和槽進行通信,彼此之間不需要顯式的引用,從而實現了松耦合的設計。此外,這種機制還具有可擴展性,因為可以動態地連接和斷開信號和槽,使得系統更容易擴展和維護。
在程序開始就新建一個線程,用于專門處理圖像并計數,這樣異步的設計將鼠標事件,繪圖事件以及視頻處理異步解耦,這種設計方式不僅提高了程序運行速度,而且使得人機交互更加靈活。
SqLite,QT-5.15.2,OpenCV-4.5.2
8.運行指導
在Windows下直接解壓壓縮包,雙擊exe文件即可執行;啟動之初會加載一段時間,這是機器學習的過程;由于沒有外接攝像頭,因此只在可執行程序目錄下放了一段模擬視頻,視頻較短,視頻播放結束程序就停止運行。