这个实例虽然简单,但也是个完整的图像识别的过程,每一步都值得初学者仔细推敲,思考为什么要这样做,知识点是否有遗漏。我们知道,图像识别的关键在于提取特征,本实例的简单之处就在于特征甚至不用想办法提取,一眼就能看出:图中的瓶盖有两个特征:红色、圆形。这两个特征足以帮助计算机识别出瓶盖,以它们为目标开始编程吧。
相对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);
得到提取红色部分的图像
瓶盖有一个很重要的特征:形状为圆形。因此,可以用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); //显示图像
得到图像的轮廓信息
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 <<"第"< 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);
}
}
得到几个可能是瓶盖的圆形轮廓
由于弯折的接线、万用表弧形边缘等物体带有一些圆形特征,也会被霍夫变换检测成圆形。但是瓶盖除了圆形轮廓外,还有一个很好的特征:整个瓶盖都为红色。因此,瓶盖对应的圆形轮廓中,红色像素点是几乎占满这个圆的。而其他误检测出的圆,红色像素点所占比例不会太高。
因此,要查看某个圆是否为瓶盖,只要检测红色像素点(亮度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
#include
#include
#include
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 <<"第"< 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;
}