OpenCV实例 识别红色瓶盖并框出

学完opencv和图像处理基础部分后,可以找一些实例来考察自己的掌握程度,难度一点一点上升。

要求:使用opencv,实现对图中瓶盖的提取,并画框圈出

这个实例虽然简单,但也是个完整的图像识别的过程,每一步都值得初学者仔细推敲,思考为什么要这样做,知识点是否有遗漏。我们知道,图像识别的关键在于提取特征,本实例的简单之处就在于特征甚至不用想办法提取,一眼就能看出:图中的瓶盖有两个特征:红色、圆形。这两个特征足以帮助计算机识别出瓶盖,以它们为目标开始编程吧。

Step1.将图片转换为HSV颜色空间并进行图像分割,保留红色像素

相对RGB颜色空间,HSV颜色空间可以更直观地反应物体颜色,十分有利于图像分割。瓶盖为红色,红色在HSV颜色空间中对应的H值为0度,在opencv中取值为(0~8)∪(160,180) 。将H值超出此区间的像素点全部设为黑色,完成图像的分割。

代码如下:

Mat colorFilter(CvMat *inputImage, CvMat *&outputImage)
{
	//inputImage为指向输入图片地址的指针,outputImage 为指向一个cvMat的指针
	int i, j;
	IplImage* image = cvCreateImage(cvGetSize(inputImage), 8, 3);  //指向空图像的指针
	cvGetImage(inputImage, image);
	IplImage* hsv = cvCreateImage(cvGetSize(image), 8, 3);       
	cvCvtColor(image, hsv, CV_BGR2HSV);              //BGR转换成HSV空间
	int width = hsv->width;
	int height = hsv->height;
	for (i = 0; i < height; i++)
		for (j = 0; j < width; j++)
		{
			CvScalar s_hsv = cvGet2D(hsv, i, j);//获取像素点为(j, i)点的HSV的值 
			/*
				opencv 的H范围是0~180,红色的H范围大概是(0~8)∪(160,180)
				S是饱和度,一般是大于一个值,S过低就是灰色(参考值S>80),
				V是亮度,过低就是黑色,过高就是白色(参考值220>V>50)。
			*/
			CvScalar s;
			if (!(((s_hsv.val[0] > 0) && (s_hsv.val[0] < 8)) || (s_hsv.val[0] > 120) && (s_hsv.val[0] < 180)))
			{
				s.val[0] = 0;
				s.val[1] = 0;
				s.val[2] = 0;
				cvSet2D(hsv, i, j, s);    //把新图像s的值,赋给hsv指针所指向的图像
			}
		}
	outputImage = cvCreateMat(hsv->height, hsv->width, CV_8UC3);
	cvConvert(hsv, outputImage);         //把IplImage图像hsv的值转给矩阵outputImage
	Mat output = cvarrToMat(hsv);
	return output;
}

在主函数中调用

src = imread("sss.jpg");
//转化为hsv空间并截取红色区域,返回给img_red
CvMat img_temp = src;
CvMat *output;
Mat img_red = colorFilter(&img_temp, output);
namedWindow("img_red");
imshow("img_red", img_red);

得到提取红色部分的图像

Step2.Canny边缘检测,提取轮廓信息

瓶盖有一个很重要的特征:形状为圆形。因此,可以用Canny算法进行边缘检测,提取图像的轮廓信息,以进行下一步的形状识别。

代码如下:

Mat cannyMake(Mat *inputImage) 
{
	Mat caaimg ;        		//存储原图像信息  
	inputImage->copyTo(caaimg); //将原图像的信息copy给camimg
	Mat dst_canny, edge, gray;      
	dst_canny.create(src.size(), src.type());
	//将原图像转为灰度
	cvtColor(src, gray, COLOR_RGB2GRAY);
	//滤波(降噪)
	blur(gray, edge, Size(3, 3));
	//canny
	Mat outImg;
	Canny(edge, outImg, 300, 100);  //这里的300、100为保留边缘信息的上下阈值,多次尝试后手动确定了合适的值,既能保留想要的瓶盖轮廓,又能除去许多不必要的轮廓
	dst_canny = Scalar::all(0);
	src.copyTo(dst_canny, outImg);

	return outImg;
}

在主函数中调用 

Mat* inImg = &src;
Mat img_red_canny = cannyMake(inImg);
namedWindow("img_red_canny");
imshow("img_red_canny", img_red_canny);   //显示图像

得到图像的轮廓信息

Step3. 利用Hough变换,提取图像中的圆形

Hough变换将图像的像素点映射到参数空间,以提取图像中的形状信息。对于直线、圆、椭圆等形状可以很好地识别。原理比较难懂,可以直接调用opencv的cvHoughCircle()函数,完成轮廓图像中圆的识别。

代码如下

CvMemStorage* storage = cvCreateMemStorage(0);
IplImage tmp = IplImage(img_red_canny);
IplImage* arr = &tmp;
cvSmooth(arr, arr, CV_GAUSSIAN, 5, 5);  //用高斯模糊平滑图像,去除不必要的噪点(如边缘突起)
CvSeq* results = cvHoughCircles(arr, storage, CV_HOUGH_GRADIENT, 2, arr->width / 3, 300, 100, 0, 100);        				//已通过Step2确定合适的参数,并确定合适的圆半径范围

除了我们想要提取的瓶盖的圆,cvHoughCircles()还会得到一些不必要的圆。这些圆的圆心位置、半径都被保存在result变量中。在轮廓图中画出这些圆,代码如下

CvMat img_forCheck = img_red;
	for (int i = 0; i < results->total; i++)
	{
		float* p = (float*)cvGetSeqElem(results, i);        //p存储results中第i个圆的信息
		CvPoint pt = cvPoint(cvRound(p[0]), cvRound(p[1]));   //pt为圆心坐标,cvRound(p[0])为圆心横坐标,cvRound(p[1])为圆心纵坐标
		//cvRound(p[2])为圆的半径
		float r = cvRound(p[2]);
		//调用checkRate函数,查看此圆是否符合要求
		float rate = checkRate(&img_forCheck,pt, r);
		cout <<"第"<<i<<"个圆——圆心:("<< pt.x<<","<<pt.y<<")  半径: "<<cvRound(p[2])<<"  ";
		cout << "占比rate = " << rate << endl;
		cvCircle(arr, pt, r, CV_RGB(0xff, 0xff, 0xff), 3, 8);   //在arr对应的位置绘制圆
		//**********************下段代码在Step4中讲解****************************
        if (rate > 0.9) {
			CvPoint pt1 = cvPoint(cvRound(p[0])-r*1.1, cvRound(p[1])-r * 1.1);
			CvPoint pt2 = cvPoint(cvRound(p[0]) + r * 1.1, cvRound(p[1]) + r * 1.1);
			cvRectangle(src_out_out, pt1, pt2, CV_RGB(0xff, 0xff, 0xff), 3, 8);   
		}
	}

得到几个可能是瓶盖的圆形轮廓

Step4.判断哪个圆形为瓶盖的轮廓,完成瓶盖识别

由于弯折的接线、万用表弧形边缘等物体带有一些圆形特征,也会被霍夫变换检测成圆形。但是瓶盖除了圆形轮廓外,还有一个很好的特征:整个瓶盖都为红色。因此,瓶盖对应的圆形轮廓中,红色像素点是几乎占满这个圆的。而其他误检测出的圆,红色像素点所占比例不会太高。

因此,要查看某个圆是否为瓶盖,只要检测红色像素点(亮度V大于90)的数量 num ,除以此圆的面积 S

得到红色像素点所占比例 rate ,若 大于 95% (接近占满),即为瓶盖。

代码如下

float checkRate(CvMat * inputImage2,CvPoint pt,int r)
{	
	float rate,num=0;
	CvSize zzz = cvGetSize(inputImage2);
	IplImage* image_rate = cvCreateImage(zzz, 8, 3);  //指向空图像的指针
	cvGetImage(inputImage2, image_rate);
	int i, j;
	int row_low = pt.y - r; if (row_low < 0)row_low = 0;
	int row_high = pt.y + r; if (row_high > 763)row_high = 763;
	int col_low = pt.x - r; if (col_low < 0)col_low = 0;
	int col_high = pt.x + r; if (col_high > 763)col_high = 763;
	for (i = row_low; i < row_high; i++) {
		for (j = col_low; j < col_high; j++) {
			CvScalar s_hsv = cvGet2D(image_rate, i, j);//获取像素点为(j, i)点的HSV的值 
			if (s_hsv.val[2] > 90)    //亮度大于90,可以认为此像素点满足要求,num数加一
			{
				num=num+1;
			}
		}
	} 
	rate = 4/3.14159*num / ((row_high - row_low)*(col_high - col_low));//rate表达式
	return rate;
}

在主函数中调用,输出这些圆的坐标信息与rate比例

可以看到,圆心为(285,551)的圆,rate达到了0.9997,可以认定为瓶盖。

用矩形框出瓶盖,代码如下

if (rate > 0.9) {
			CvPoint pt1 = cvPoint(cvRound(p[0])-r*1.1, cvRound(p[1])-r * 1.1);
			CvPoint pt2 = cvPoint(cvRound(p[0]) + r * 1.1, cvRound(p[1]) + r * 1.1);
			cvRectangle(src_out_out, pt1, pt2, CV_RGB(0xff, 0xff, 0xff), 3, 8);   
}

效果图如下:

红色瓶盖就这样识别出来啦!下面附上完整的程序

完整程序

#include "pch.h"
#include <iostream>
#include<opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp> 
#include<opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

Mat src;

int edgeThresh = 1;
int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
const char* window_name = "Edge Map";  //指向char型常数的指针
const char* window_name2 = "sssssEdge Map";  //指向char型常数的指针
//截取图像中红色区域
Mat colorFilter(CvMat *inputImage, CvMat *&outputImage)
{
	//inputImage为指向输入图片地址的指针,outputImage 为指向一个cvMat的指针
	int i, j;
	IplImage* image = cvCreateImage(cvGetSize(inputImage), 8, 3);  //指向空图像的指针
	cvGetImage(inputImage, image);
	IplImage* hsv = cvCreateImage(cvGetSize(image), 8, 3);       
	cvCvtColor(image, hsv, CV_BGR2HSV);              //BGR转换成HSV空间
	int width = hsv->width;
	int height = hsv->height;
	for (i = 0; i < height; i++)
		for (j = 0; j < width; j++)
		{
			CvScalar s_hsv = cvGet2D(hsv, i, j);//获取像素点为(j, i)点的HSV的值 
			/*
				opencv 的H范围是0~180,红色的H范围大概是(0~8)∪(160,180)
				S是饱和度,一般是大于一个值,S过低就是灰色(参考值S>80),
				V是亮度,过低就是黑色,过高就是白色(参考值220>V>50)。
			*/
			CvScalar s;
			if (!(((s_hsv.val[0] > 0) && (s_hsv.val[0] < 8)) || (s_hsv.val[0] > 120) && (s_hsv.val[0] < 180)))
			{
				s.val[0] = 0;
				s.val[1] = 0;
				s.val[2] = 0;
				cvSet2D(hsv, i, j, s);    //把新图像s的值,赋给hsv指针所指向的图像
			}
		}
	outputImage = cvCreateMat(hsv->height, hsv->width, CV_8UC3);
	cvConvert(hsv, outputImage);         //把IplImage图像hsv的值转给矩阵outputImage
	Mat output = cvarrToMat(hsv);
	return output;
}

//canny边缘信息提取
Mat cannyMake(Mat *inputImage) 
{
	Mat caaimg ;					//存储原图像信息  
	inputImage->copyTo(caaimg);		//将原图像的信息copy给camimg

	Mat dst_canny, edge, gray;      
	dst_canny.create(src.size(), src.type());
	//将原图像转为灰度
	cvtColor(src, gray, COLOR_RGB2GRAY);
	//滤波(降噪)
	blur(gray, edge, Size(3, 3));
	//canny
	Mat outImg;
	Canny(edge, outImg, 300, 100);
	dst_canny = Scalar::all(0);
	src.copyTo(dst_canny, outImg);

	return outImg;
}

float checkRate(CvMat * inputImage2,CvPoint pt,int r)
{	
	float rate,num=0;
	CvSize zzz = cvGetSize(inputImage2);
	IplImage* image_rate = cvCreateImage(zzz, 8, 3);  //指向空图像的指针
	cvGetImage(inputImage2, image_rate);
	int i, j;
	int row_low = pt.y - r; if (row_low < 0)row_low = 0;
	int row_high = pt.y + r; if (row_high > 763)row_high = 763;
	int col_low = pt.x - r; if (col_low < 0)col_low = 0;
	int col_high = pt.x + r; if (col_high > 763)col_high = 763;
	for (i = row_low; i < row_high; i++) {
		for (j = col_low; j < col_high; j++) {
			CvScalar s_hsv = cvGet2D(image_rate, i, j);//获取像素点为(j, i)点的HSV的值 
			if (s_hsv.val[2] > 90) 
			{
				num=num+1;
			}
		}
	} 
	rate = 4/3.14159*num / ((row_high - row_low)*(col_high - col_low));
	return rate;
}

int main(int argc, char** argv)
{
	Mat src_out = imread("sss.jpg");
	IplImage src_out_temp = (IplImage)src_out;
	IplImage* src_out_out = &src_out_temp;
	src = imread("sss.jpg");
	//转化为hsv空间并截取红色区域,返回给img_red
	CvMat img_temp = src;
	CvMat *output;
	Mat img_red = colorFilter(&img_temp, output);

	src = img_red;                
	namedWindow("img_red");
	imshow("img_red", img_red);
	
	Mat* inImg = &src;
	Mat img_red_canny = cannyMake(inImg);
	
	//显示图像
	namedWindow("img_red_canny");
	imshow("img_red_canny", img_red_canny);

	CvMemStorage* storage = cvCreateMemStorage(0);

	IplImage tmp = IplImage(img_red_canny);

	IplImage* arr = &tmp;
	cvSmooth(arr, arr, CV_GAUSSIAN, 5, 5);
	CvSeq* results = cvHoughCircles(arr, storage, CV_HOUGH_GRADIENT, 2, arr->width / 3, 300, 100, 0, 100);
	
	CvMat img_forCheck = img_red;
	for (int i = 0; i < results->total; i++)
	{
		float* p = (float*)cvGetSeqElem(results, i);        //p存储results中第i个圆的信息
		CvPoint pt = cvPoint(cvRound(p[0]), cvRound(p[1]));   //pt为圆心坐标,cvRound(p[0])为圆心横坐标,cvRound(p[1])为圆心纵坐标
		//cvRound(p[2])为圆的半径
		float r = cvRound(p[2]);

		//这里应该写一个函数,判断圆是否符合要求
		float rate = checkRate(&img_forCheck,pt, r);
		cout <<"第"<<i<<"个圆——圆心:("<< pt.x<<","<<pt.y<<")  半径: "<<cvRound(p[2])<<"  ";
		cout << "占比rate = " << rate << endl;
		cvCircle(arr, pt, r, CV_RGB(0xff, 0xff, 0xff), 3, 8);   //在arr对应的位置绘制圆
		if (rate > 0.9) {
			CvPoint pt1 = cvPoint(cvRound(p[0])-r*1.1, cvRound(p[1])-r * 1.1);
			CvPoint pt2 = cvPoint(cvRound(p[0]) + r * 1.1, cvRound(p[1]) + r * 1.1);
			cvRectangle(src_out_out, pt1, pt2, CV_RGB(0xff, 0xff, 0xff), 3, 8);  
		}
	}
	cvNamedWindow("Img", 1);
	cvShowImage("Img", arr);
	
	cvNamedWindow("效果图", 1);
	cvShowImage("效果图", src_out_out);
	//cvSaveImage("效果图.jpg", src_out_out);
	waitKey(0);
	return 0;

}

  • 37
    点赞
  • 212
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值