目录
前言
一、项目要求
二、项目分析
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;
}
如果我们更换一下图片,我们使用未亮灯图片,没有提示,也没有任何改变: