今天一位技术网友碰到一个这样的问题,就是在同一个角度对同一个杯子拍照,那么如何判断杯子里有水无水(该文章经得网友同意后发布)。
如上图所示,第一个杯子当中无水,第二个杯子当中有水。
好在规律是从上面往下拍照的过程当中,有水的杯子的会反射出一条明显的白色曲线。
最后我们一致认为,需要使用OpenCV 的直线检测方法来判断这条反射线的存在。
这样问题就比较简单了,最后利用openCV的HoughLines方法(霍夫直线检测),30多行代码搞定这个问题。
亮出代码之间,我们首先了解下霍夫直线检测。
OpenCV当中,有一种叫做霍夫变换的图像空间变换。类似于傅里叶变换,都是对原坐标系的“重新洗牌”,都是一种从另一个角度来看待图像像素点的一种变换。
对于霍夫变换来说,有很多文章阐述其原理,在这里就不再重复了。
简单来说,经过霍夫变换以后满足:
1)原图像空间中的一条直线,成为霍夫空间中的点。
2)原图像空间中的点,成为霍夫空间中的一条直线
3)霍夫空间中相交的直线,成为原空间中在同一条直线上的点。
openCV利用规则(3)检测图像中的直线。即,通过对相同像素值的点进行叠加,只要在霍夫空间中找到很多相交于一点的直线,那么该点就必然对应原图像中的一条直线。
又因为垂直线的在经过霍夫变换时会遇到分母为0的情况,所以采用极坐标代替笛卡尔坐标来表示原图。
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标定空杯的线条数,所以一开始应该传入空杯图像,计算这个阈值。以后再根据线条数大于或小于这个阈值来判断杯中有水无水。
另外,图像的大小改变,也会影响到这个阈值
最终效果图
最后,大家如果有更好更专业的检测方法,欢迎不吝赐教,在下面留言给我。