原意是想做一个捡网球的机器人,识别网球就是必须的。项目地址http://git.oschina.net/caogos/OpenLoongsonPickUpTennisRobot
参考了 http://www.pyimagesearch.com/2015/09/14/ball-tracking-with-opencv/
主要思路
用cvtColor()将图像转换到HSV颜色空间,用inRange()根据网球颜色提取出网球,通过形态学变换(膨胀和腐蚀)除去网球上的条纹和字,使识别出的网球更圆,用findContours()找轮廓,再用minEnclosingCircle()找轮廓的最小外接圆。
源码
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <sys/time.h>
#include "v4l2_capture_picture.h"using namespace std;
using namespace cv;#define CAPTURE_PICTURE_PATH "/test/PickUpTennisRobot/capture_picture.jpg"// 图片中网球的最小半径,注意不是实际的网球半径
#define MIN_CIRCLE_RADIUS (25)// 打印当前时间
void PrintCurrentTime(void)
{struct timeval tv;gettimeofday(&tv, NULL);cout << tv.tv_sec << "." << tv.tv_usec << endl;
}int main(int argc, char** argv)
{Mat src, src_gray;int ret = 0;/*// 通过摄像头抓拍ret = capture_picture(CAPTURE_PICTURE_PATH);if (0 != ret){std::cout << "capture_picture fail." << std::endl;return ret;}
*//// Read the imagesrc = imread(CAPTURE_PICTURE_PATH);if( !src.data ){cout << "imread src picture fail." << endl;return -1; }PrintCurrentTime();// 转换为hsv颜色空间Mat hsv;cvtColor(src, hsv, CV_BGR2HSV);// 通过颜色找网球// 这里是识别的关键,请仔细研究百度百科的"HSV颜色空间"// 理论上:H的取值范围是0-360,S是0%-100%即0-1,V是0%-100%即0-1// 实际上opencv在实现上做了调整:H的取值范围为0-180,S为0-255,V为0-255// 比如理论上绿色的H值为120,在opencv里绿色为60,即理论值的一半// 调试方法为把S和V的范围选为0-255,根据实物的颜色确定H值,// H值确定得好基本上可以过滤出想要的效果,然后通过S和V来优化// 比如网球可以先把范围选为Scalar(45,0,0)到Scalar(75,255,255),运行结果显示白色区域太多,未能过滤出网球// 继续缩小H值为Scalar(50,0,0)到Scalar(70,255,255),运行结果中可以看到网球了,但是还不理想// 继续缩小,调整范围为Scalar(30,0,0)到Scalar(60,255,255),这时inRange已经能准确过滤出网球了// 最后通过S和V的值来微调,比如过滤掉网球的影子等Mat mask;inRange(hsv, Scalar(45,5, 200), Scalar(60,255,255), mask);imwrite("inRange.jpg", mask);// 通过膨胀、腐蚀,去除网球上的字和条纹int structElemSize = 10;Mat dilate_dst, erode_dst;Mat element = getStructuringElement(MORPH_CROSS, Size(2*structElemSize+1, 2*structElemSize+1));dilate(mask, dilate_dst, element);
// imwrite("dilate_dst.jpg", dilate_dst);erode(dilate_dst, erode_dst, element);imwrite("erode_dst.jpg", erode_dst);// 获取轮廓,圆vector<vector<Point> > contours;findContours(erode_dst, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);cout << "contours size=" << contours.size() << endl;
/*// 画出轮廓Mat matContours(src.size(), CV_8U, Scalar(255));// 参数为:画板,轮廓,轮廓指示(这里画出所有轮廓),颜色,线粗drawContours(matContours, contours, -1, Scalar(0), 2);imwrite("matContours.bmp", matContours);Mat srcContours = imread(CAPTURE_PICTURE_PATH);drawContours(srcContours, contours, -1, Scalar(0,0,255), 2);imwrite("matContours_src.bmp", srcContours);
*/// 最小外接圆Mat matMinEnclosingCircle = imread(CAPTURE_PICTURE_PATH);float radius;Point2f center;for (size_t i=0; i<contours.size(); i++){// 计算轮廓的最小外接圆minEnclosingCircle(Mat(contours[i]), center, radius);// 过滤掉半径太小的外接圆(半径太小的不是网球)if (MIN_CIRCLE_RADIUS > radius){continue;}// 画出最小外接圆circle(matMinEnclosingCircle, center, static_cast<int>(radius), Scalar(0,0,255), 2);cout << i+1 << " cirecle:" << " x=" << center.x << ", y=" << center.y << ", radius=" << radius << endl;}imwrite("minEnclosingCircle.bmp", matMinEnclosingCircle);PrintCurrentTime();return 0;
}
Makefile
all:mipsel-linux-gcc -Wall -c v4l2_capture_picture.c -o v4l2_capture_picture.omipsel-linux-gcc -Wall -c main.cpp -o main.o -I/home/develop/opencv/opencv-2.4.9-ls1x-lib/includemipsel-linux-g++ *.o -o PickUpTennisRobot -L/home/develop/opencv/opencv-2.4.9-ls1x-lib/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lpthread -lrtcp PickUpTennisRobot /nfsramdisk/LS1xrootfs-demo/test/PickUpTennisRobotclean:rm *.o PickUpTennisRobot
重难点
对HSV的理解和正确使用函数inRange()是关键。上面源码中已经写了很多注释了,多看几遍百度百科里的“HSV颜色空间”,同时要正确理解函数inRange()每个参数的意思。
形态学变换(腐蚀和膨胀)有利于提高识别的准确性,这里耗时最多。
识别的准确性,可以通过最小外接圆与网球的贴合程度,越靠近,则精度越高。
实际应用中需要在精度和速度上平衡。
同一张图片笔记本屏幕看到的和台式机的液晶显示器看到的有差别,另外,我在pc上用qt做的和龙芯1c开发板上对HSV中的V(明暗程度)有些不一样,理论上pc上和开发板上运行结果应该一样的,这个还不知道原因。
运行效果
开发板上
原图
inRange()得到的
膨胀
腐蚀
轮廓
最小外接圆(背景为原图)