用霍夫线段检测来判断杯子有没有水

今天一位技术网友碰到一个这样的问题,就是在同一个角度对同一个杯子拍照,那么如何判断杯子里有水无水(该文章经得网友同意后发布)。
用霍夫线段检测来判断杯子有没有水_第1张图片
用霍夫线段检测来判断杯子有没有水_第2张图片

如上图所示,第一个杯子当中无水,第二个杯子当中有水。

好在规律是从上面往下拍照的过程当中,有水的杯子的会反射出一条明显的白色曲线。

最后我们一致认为,需要使用OpenCV 的直线检测方法来判断这条反射线的存在。

这样问题就比较简单了,最后利用openCV的HoughLines方法(霍夫直线检测),30多行代码搞定这个问题。

亮出代码之间,我们首先了解下霍夫直线检测。

OpenCV当中,有一种叫做霍夫变换的图像空间变换。类似于傅里叶变换,都是对原坐标系的“重新洗牌”,都是一种从另一个角度来看待图像像素点的一种变换。
对于霍夫变换来说,有很多文章阐述其原理,在这里就不再重复了。
简单来说,经过霍夫变换以后满足:
1)原图像空间中的一条直线,成为霍夫空间中的点。
2)原图像空间中的点,成为霍夫空间中的一条直线
3)霍夫空间中相交的直线,成为原空间中在同一条直线上的点。

用霍夫线段检测来判断杯子有没有水_第3张图片

openCV利用规则(3)检测图像中的直线。即,通过对相同像素值的点进行叠加,只要在霍夫空间中找到很多相交于一点的直线,那么该点就必然对应原图像中的一条直线。

又因为垂直线的在经过霍夫变换时会遇到分母为0的情况,所以采用极坐标代替笛卡尔坐标来表示原图。

用霍夫线段检测来判断杯子有没有水_第4张图片
openCV中,通过函数HoughLines来检测直线是否存在,在此,你只需根据具体情况输入相应参数(检测精度,阈值等),就可以让计算机帮你在图像中找到所有直线,函数最终返回一个直线集合。该函数有一个扩展就是HoughLinesP,用来检测线段


void HoughLinesP(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength=0,
double maxLineGap=0 );


经过以上简单了解,我们来看以下HoughLinesP函数的参数及其意义:
1) Img:被检测原图像。
2) Lines: 输出的直线集合。
3) Rho:极坐标中的半径的精度,一般为1。
4) Theta: 极坐标中的角度的精度,一般为π/180。
5) Thres:阈值,值越小检测出的直线越多,反之越少。
6) MinLineLengh:线段的最小长度,默认是0。
7) MaxLineGap:线段的最大间隔,默认为0。

上面判断同一角度去看同一杯子有水,无水的问题,就是利用HoughLinesP这个函数进行线段检测,杯子盛了水了,就会有一些曲线反射出来。所以我们就检测可以得到的线段数量,有水的杯子总比无水的杯子多很多线段。
代码如下

// line1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _CRT_SECURE_NO_WARNINGS

#include 
#include 

using namespace std;
using namespace cv;


#define EMPTY_THRES 5

#define BI_THRES 80
#define HOUGH_THRES 50
#define LINE_LEN 70
#define LINE_GAP 30
int main(int argc, const char** argv)
{
    cv::CommandLineParser parser(argc, argv,
        "{imagePath|D:/试验/test/line/cup_water.jpg|}");
    string inputName = parser.get<string>("imagePath");
    //读入原图像
    cv::Mat image = imread(inputName);
    cv::Mat gray, bi;
    //原图像转化为灰度图以便进一步处理
    cvtColor(image, gray, COLOR_BGR2GRAY);
    //图像平滑处理,消除噪声。
    medianBlur(gray, gray, 5);

    //转换为二值图像
    threshold(gray, bi, BI_THRES, 255, THRESH_BINARY);
    //初步取得图像边界
    Canny(bi, bi, BI_THRES, 255);

    //检测图像中的线段,返回线段集合
    vector<Vec4f> lines;
    cv::HoughLinesP(bi, lines, 1, CV_PI / 180, HOUGH_THRES, LINE_LEN, LINE_GAP);

    //判断线段集合的数量,多于一定阈值,就认为杯子有水
    int line_count = lines.size();
    if (line_count <= EMPTY_THRES)
    {
        cout << "没水" << endl;
    }
    else
    {
        cout << "有水" << endl;
    }
  
    for (size_t i = 0; i < lines.size(); i++)
    {
        Vec4f line = lines[i];
        cv::line(image, Point(line[0], line[1]), Point(line[2], line[3]), Scalar(0, 255, 0),2);
    }
    
    imshow("效果图窗口", image);
    cv::waitKey(0);
   
}

注意:
这里阈值EMPTY_THRES标定空杯的线条数,所以一开始应该传入空杯图像,计算这个阈值。以后再根据线条数大于或小于这个阈值来判断杯中有水无水。
另外,图像的大小改变,也会影响到这个阈值

最终效果图

用霍夫线段检测来判断杯子有没有水_第5张图片用霍夫线段检测来判断杯子有没有水_第6张图片

最后,大家如果有更好更专业的检测方法,欢迎不吝赐教,在下面留言给我。

你可能感兴趣的:(opencv,计算机视觉,opencv,图像处理)