目錄
前言
一、項目要求
二、項目分析
1、擷取圖像
2、選擇區域
3、區域像素檢測
4、報警
三、實戰
1、擷取圖像
2、選擇區域
3、區域像素檢測
4、報警
5、全部代碼
前言
大學時候,曾經暑假在學校值班,當時一個學長在準備考研,學長是我在計算機視覺的引路人,當時我們要做一件事,就是監控火警燈,因為火警報警器出故障了,當時學長跟我說,可以做一個自動識别然後進行報警的裝置。這樣就不用每時每刻都盯着它,就可以把精力放在其他事情上。
這個項目化簡一下,我們就可以變成如下這個項目,用我們最近學習的讀寫像素就可以完成啦。
一、項目要求
下面是火警等一系列監控界面,監控正常情況如下:
火警隻有在有地方的煙感裝置監測到有煙出現,才會亮燈并且報警。
當監控出現問題時,故障就會亮燈。
但是監控器出故障之後:
火警預警燈就會定期閃亮,并且沒有報警;
故障燈一直常亮狀态;
為了防患于未然,學校的要求是,隻要有火警燈亮起,就必須到報警地檢視情況,如果沒有問題,回到監控室,手動關閉火警燈。
現在我們要做的就是通過攝像頭自動監控火警燈,如果亮起,發出警報!
火警燈亮起
火警燈未亮
二、項目分析
我們目前學習了幾個最基本的内容:
圖像輸入、輸出、儲存
Mat類
基本資料類型
像素基本操作(擷取像素指針、控制像素範圍、讀寫像素)
我們就要用上面的來完成該項目。
我們分析一下這個項目,主要分為如下步驟:
1、擷取圖像
原項目是通過視訊流擷取圖像,現在我們沒有具體得環境,就隻能用圖檔,檢測原理是一樣的。我們要先擷取圖像,保證圖像能夠正确讀取,才能完成後續操作。
2、選擇區域
周遊所有圖像的運算速度是非常慢的,為了加快運算效率,我們要選擇縮小區域進行檢測。是以我們要選擇如下區域:
因為隻有上面的幾個基本内容,那我們就沒有辦法使用更多的東西進行定位,我們定位的方式就是通過反複檢測,然後找到一個合适的區域:
(1)盡可能隻包含火警區域;
(2)不受攝像頭抖動影響,是以攝像頭輕微抖動,火警燈依然在範圍内;
3、區域像素檢測
擷取火警燈位置的像素,并根據像素值標明亮燈及滅燈像素範圍。
但是我們發現,滅燈狀态,和火警外面的圓角矩形邊框的顔色是類似的,是以檢測滅燈容易出現誤報。而亮燈的燈光像素應該是很明顯的,再加上燈隻有燈亮和燈滅兩種狀态,是以我們隻需要檢測亮燈的像素範圍就好。在像素範圍内,就說明亮燈,不在像素範圍内,就說明滅燈。
當我們确定了亮燈的像素範圍後,我們就需要實時監測我們選擇的區域像素,并判斷是否有像素在我們上面規定好的範圍之内。
4、報警
一旦出現有大量的像素點滿足在範圍之内,那我們就報警。是以我們要設定一個門檻值,達到多少時,我們認為是火警燈亮。
為什麼我們要檢測到大量呢?
因為根據環境光線不同,一旦出現反光,也會導緻出現部分點滿足範圍。
三、實戰
1、擷取圖像
首先我們要擷取圖像。這個用到我們最開始的圖像的讀入,理論上我們是要使用攝像頭,然後擷取幀,在這裡,我們就直接讀入圖檔吧!這并不影響後續的操作,也沒有改變檢測原理。代碼如下:
Mat src = imread("./image/1.png"); //1.png 是亮燈, 2.png 是滅燈
if (!src.data)
{
cout << "ERROR : could not load image.\n";
return -1;
}
2、選擇區域
然後需要我們擷取區域,最直接的方法就是憑感覺去設定,然後不斷調整找到一個合适的區域。
當然如果我們學的更多的話,我們可以用到更多的功能去獲得區域,比如當時我嘗試了如下方式:
(1)給那個區域貼上紅紙,檢測紅紙;
(2)使用文字識别子產品,檢測火警文字,然後擷取區域。
現在我們學的内容比較少,就用最簡單、最直接的方式吧!
代碼如下:
//不斷調整Rect,找到對于我的比較合适為Rect(70, 60, 150, 60)。
Mat src_ROI = Mat(src, Rect(70, 60, 150, 60));
imshow("src_ROI", src_ROI);
區域如下:
3、區域像素檢測
擷取火警燈位置的像素,并根據像素值標明亮燈及滅燈像素範圍。
我們要檢測的區域為:
這是一個22×22的正方形區域,因為亮燈區域是一個圓形區域,是以,我們考慮準确度,那我們在檢測像素,應該是檢測到圖像中心距離為11像素範圍内的圖像:
int max_B = 0, max_G = 0, max_R = 0;
int min_B = 255, min_G = 255, min_R = 255;
//半徑為9的圓區域像素
for (int i = 0; i < light.rows; i++)
{
for (int j = 0; j < light.cols; j++)
{
if ((i - 11)*(i - 9) + (j - 9)*(j - 9) <= 81) {
Vec3b BGR = light.at<Vec3b>(i, j);
int B = BGR.val[0];
int G = BGR.val[1];
int R = BGR.val[2];
cout << "[" << B << ", " << G << ", " << R << "]\t";
if (B > max_B) max_B = B;
else if (B < min_B) min_B = B;
if (G > max_G) max_G = G;
else if (G < min_G) min_B = G;
if (R > max_R) max_R = R;
else if (R < min_R) min_R = R;
}
}
cout << endl << endl;
}
cout << "B: [" << min_B << ", " << max_B << "]" << endl;
cout << "G: [" << min_G << ", " << max_G << "]" << endl;
cout << "R: [" << min_R << ", " << max_R << "]" << endl;
檢測的結果如下:
如果想讓精度更高,我們可以縮小範圍,比如到圖像中心距離為10那麼檢測到的結果如下:
我們可以根據上面我們獲得的範圍,來選擇我們的範圍,比如我們的像素範圍可以是:
B : ≥125;G : ≥ 155;R : ≥ 228;
4、報警
一個半徑為10的圓形中間的像素點的個數肯定大于邊長為10的正方形中間的像素點的個數。
邊長為10的正方形有100個像素點。那我們的門檻值,可以設為100,隻要我們檢測到滿足上面的範圍的像素點的個數大于100時,就認為火警燈亮起。為了檢視哪些點被檢測出來,我們将這些點的像素值修改為黑色。
火警燈亮起,我們給出提示。
int num = 0;
//按列周遊,效率更高
for (int i = 0; i < src_ROI.cols; i++)
{
for (int j = 0; j < src_ROI.rows; j++)
{
BGR = src_ROI.at<Vec3b>(j, i);
if (BGR.val[0] >= min_B && BGR.val[1] >= min_G && BGR.val[2] >= min_R) {
src_ROI.at<Vec3b>(j, i)[0] = 0;
src_ROI.at<Vec3b>(j, i)[1] = 0;
src_ROI.at<Vec3b>(j, i)[2] = 0;
num++;
//cout << "num = " << num << endl;
}
}
}
if (num > 100) {
cout << "親,疑似火警,請親臨現場檢視!" << endl;
}
imshow("new_src_ROI", src_ROI);
結果如下:
5、全部代碼
全部代碼如下:
/*
作者:水亦心
内容:core-像素基本操作實戰之報警燈檢測
時間:2020年5月20日
*/
#define INPUT_TITLE "input image"
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace std;
using namespace cv;
int main() {
擷取圖像
Mat src = imread("./image/1.png"); //1.png 是亮燈, 2.png 是滅燈
if (!src.data)
{
cout << "ERROR : could not load image.\n";
return -1;
}
選擇區域
//不斷調整Rect,找到對于我的比較合适為Rect(70, 60, 150, 60)。
Mat src_ROI = Mat(src, Rect(70, 60, 150, 60));
imshow("src_ROI", src_ROI);
// 區域像素檢測 //
Mat light = imread("./image/3.png"); //光區域
if (!light.data)
{
cout << "ERROR : could not load light image.\n";
return -1;
}
int max_B = 0, max_G = 0, max_R = 0;
int min_B = 255, min_G = 255, min_R = 255;
int B, G, R;
Vec3b BGR;
//半徑為9的圓區域像素
int k = 0;
for (int i = 0; i < light.rows; i++)
{
for (int j = 0; j < light.cols; j++)
{
if ((i - 11)*(i - 11) + (j - 11)*(j - 11) <= 100) {
BGR = light.at<Vec3b>(i, j);
B = BGR.val[0];
G = BGR.val[1];
R = BGR.val[2];
//cout << "[" << B << ", " << G << ", " << R << "]\t";
//light.at<Vec3b>(i, j)[0] = 0;
//light.at<Vec3b>(i, j)[1] = 0;
//light.at<Vec3b>(i, j)[2] = 0;
//imshow("new light", light);
if (B > max_B) max_B = B;
if (B < min_B) min_B = B;
if (G > max_G) max_G = G;
if (G < min_G) min_G = G;
if (R > max_R) max_R = R;
if (R < min_R) min_R = R;
}
}
//cout << endl << endl;
}
cout << "B: [" << min_B << ", " << max_B << "]" << endl;
cout << "G: [" << min_G << ", " << max_G << "]" << endl;
cout << "R: [" << min_R << ", " << max_R << "]" << endl;
cout << endl;
//Mat test = Mat(100, 100, CV_8UC3, Scalar(min_B, min_G, min_R));
//imshow("test", test);
監測及報警 ///
int num = 0;
//按列周遊,效率更高
for (int i = 0; i < src_ROI.cols; i++)
{
for (int j = 0; j < src_ROI.rows; j++)
{
BGR = src_ROI.at<Vec3b>(j, i);
if (BGR.val[0] >= min_B && BGR.val[1] >= min_G && BGR.val[2] >= min_R) {
src_ROI.at<Vec3b>(j, i)[0] = 0;
src_ROI.at<Vec3b>(j, i)[1] = 0;
src_ROI.at<Vec3b>(j, i)[2] = 0;
num++;
//cout << "num = " << num << endl;
}
}
}
if (num > 100) {
cout << "親,疑似火警,請親臨現場檢視!" << endl;
}
imshow("new_src_ROI", src_ROI);
waitKey(0);
return 0;
}
如果我們更換一下圖檔,我們使用未亮燈圖檔,沒有提示,也沒有任何改變: