天天看点

【OpenCV实战】报警灯检测装置(像素操作)

目录

​​前言​​

​​一、项目要求​​

​​二、项目分析​​

​​1、获取图像​​

​​2、选择区域​​

​​3、区域像素检测​​

​​4、报警​​

​​三、实战​​

​​1、获取图像​​

​​2、选择区域​​

​​3、区域像素检测​​

​​4、报警​​

​​5、全部代码​​

前言

大学时候,曾经暑假在学校值班,当时一个学长在准备考研,学长是我在计算机视觉的引路人,当时我们要做一件事,就是监控火警灯,因为火警报警器出故障了,当时学长跟我说,可以做一个自动识别然后进行报警的装置。这样就不用每时每刻都盯着它,就可以把精力放在其他事情上。

这个项目化简一下,我们就可以变成如下这个项目,用我们最近学习的读写像素就可以完成啦。

一、项目要求

下面是火警等一系列监控界面,监控正常情况如下:

火警只有在有地方的烟感设备监测到有烟出现,才会亮灯并且报警。

当监控出现问题时,故障就会亮灯。

但是监控器出故障之后:

火警预警灯就会定期闪亮,并且没有报警;

故障灯一直常亮状态;

为了防患于未然,学校的要求是,只要有火警灯亮起,就必须到报警地查看情况,如果没有问题,回到监控室,手动关闭火警灯。

现在我们要做的就是通过摄像头自动监控火警灯,如果亮起,发出警报!

【OpenCV实战】报警灯检测装置(像素操作)

火警灯亮起

【OpenCV实战】报警灯检测装置(像素操作)

火警灯未亮

二、项目分析

我们目前学习了几个最基本的内容:

图像输入、输出、保存

Mat类

基本数据类型

像素基本操作(获取像素指针、控制像素范围、读写像素)

我们就要用上面的来完成该项目。

我们分析一下这个项目,主要分为如下步骤:

1、获取图像

原项目是通过视频流获取图像,现在我们没有具体得环境,就只能用图片,检测原理是一样的。我们要先获取图像,保证图像能够正确读取,才能完成后续操作。

2、选择区域

遍历所有图像的运算速度是非常慢的,为了加快运算效率,我们要选择缩小区域进行检测。所以我们要选择如下区域:

【OpenCV实战】报警灯检测装置(像素操作)
【OpenCV实战】报警灯检测装置(像素操作)

因为只有上面的几个基本内容,那我们就没有办法使用更多的东西进行定位,我们定位的方式就是通过反复检测,然后找到一个合适的区域:

(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);      

区域如下:

【OpenCV实战】报警灯检测装置(像素操作)

3、区域像素检测

获取火警灯位置的像素,并根据像素值选定亮灯及灭灯像素范围。

我们要检测的区域为:

【OpenCV实战】报警灯检测装置(像素操作)

这是一个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;      

检测的结果如下:

【OpenCV实战】报警灯检测装置(像素操作)

如果想让精度更高,我们可以缩小范围,比如到图像中心距离为10那么检测到的结果如下:

【OpenCV实战】报警灯检测装置(像素操作)

我们可以根据上面我们获得的范围,来选择我们的范围,比如我们的像素范围可以是:

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);      

结果如下:

【OpenCV实战】报警灯检测装置(像素操作)
【OpenCV实战】报警灯检测装置(像素操作)

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;
}      

如果我们更换一下图片,我们使用未亮灯图片,没有提示,也没有任何改变:

【OpenCV实战】报警灯检测装置(像素操作)