利用opencv识别圆环找出圆心
方法思路
大概的流程就是: 先对图像去噪声
识别出轮廓
找出自己想要的轮廓(重点)
涉及到的opencv方法
1 cv::GaussianBlur() //彩色图像转化成灰度图,并且去噪
2 cv::threshold() //图像二值化,用法以后会根据我的经验详解一次
3 cv::findContours() //找图像轮廓
4 cv::fitEllipse() //椭圆拟合
其实用到的opencv的封装方法还有很多,在这解释也没有必要,主要用这四个就能实现了,简单又方便。
关键代码
void markDetect(cv::Mat image, std::vector<cv::Point2f> &marks){
cv::Mat smoothed_gray_gpu, fd_x, fd_y, fd_mag, fd_mag_8bit_abs, edge, smoothed_gray, edge_;
cv::GaussianBlur(image, smoothed_gray_gpu, cv::Size(3, 3), 0);//彩色图像转化成灰度图
// smoothed_gray = smoothed_gray_gpu.clone();
// cv::Sobel(smoothed_gray_gpu, fd_x, CV_32F, 1, 0, 1);//边缘检测
// cv::Sobel(smoothed_gray_gpu, fd_y, CV_32F, 0, 1, 1);
// cv::magnitude(fd_x, fd_y, fd_mag);//梯度幅值
// fd_mag.convertTo(fd_mag_8bit_abs, CV_8UC1, 1);
cv::threshold(smoothed_gray_gpu, edge, 127, 255, cv::THRESH_TOZERO);
std::vector<std::vector<cv::Point> > contours;
cv::cvtColor(edge, edge_, CV_BGR2GRAY);
cv::findContours(edge_, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
cv::drawContours(image, contours, -1, cv::Scalar::all(255));
bool is_mark;
float ellipseness, eccenty, radius;
cv::RotatedRect ellipse;
cc_ellipse_.clear();
//std::cout <<"contours.size()="<< contours.size() << std::endl;
for (size_t i=0; i<contours.size(); i++) {//遍历轮廓,找圆
if (contours[i].size() < 5)
continue;
if(cv::contourArea(contours[i]) < min_contour_area_)
{
continue;
}
ellipseness = FitEllipse(contours[i], ellipse);//和圆的相似程度
if (!InsideImage(image, ellipse.center)) continue;
radius = EllipseRadius(ellipse);//半径
eccenty = EllipseEccentricity(ellipse);//圆心
is_mark = (ellipseness <= max_ellipseness_
&& radius >= min_radius_
&& eccenty <= max_eccentricity_
/*&& data >= min_center_grayscale_*/);
std::cout<<"ismark="<<is_mark<<std::endl;
if (is_mark) {
UpateConcentricEllipse(ellipse);//更新满足要求的椭圆list,同心圆
// std::cout<<"eccenty="<<eccenty<<std::endl;
cv::ellipse(image, ellipse, cv::Scalar(0,0,255));
}
}
std::cout <<"cc_ellipse_.size()"<<cc_ellipse_.size() << std::endl;
for (size_t i=0; i<cc_ellipse_.size(); i++)
{
if (RecognizeMark(cc_ellipse_[i])){
marks.push_back(cv::Point2f(cc_ellipse_[i].center()));
std::cout << "push elli"<<cc_ellipse_[i].center() << std::endl;
cv::circle(image, cc_ellipse_[i].center(), 5, cv::Scalar(255,0,0));
std::cout << cc_ellipse_[i].center() << std::endl;
}
}
}
float FitEllipse(const std::vector<cv::Point> &contour, cv::RotatedRect &ellipse)
{//返回的数值代表和圆的相似度
float dist = 0;
std::vector<cv::Point> contourEllipse;
ellipse = cv::fitEllipse(contour);
cv::Size axes(0.5*ellipse.size.width, 0.5*ellipse.size.height);
cv::ellipse2Poly(ellipse.center, axes, ellipse.angle, 0, 360, 1, contourEllipse);
for (size_t i=0; i<contour.size(); i+=3)
dist += std::abs((float)cv::pointPolygonTest(contourEllipse, contour[i], true));
return dist/contour.size();
}
bool InsideImage(const cv::Mat &image, const cv::Point &pt) {//点是否在轮廓内
cv::Rect rect(0, 0, image.cols, image.rows);
return rect.contains(pt);
}
float EllipseRadius(const cv::RotatedRect &ellipse)
{
return (ellipse.size.width + ellipse.size.height) * 0.5f;
}
float EllipseEccentricity(const cv::RotatedRect &ellipse)
{
float a = std::max(ellipse.size.width, ellipse.size.height) * 0.5f;
float b = std::min(ellipse.size.width, ellipse.size.height) * 0.5f;
return sqrt(1 - (b*b)/(a*a));
}
void UpateConcentricEllipse(const cv::RotatedRect &ellipse) {
for(size_t i=0; i<cc_ellipse_.size(); i++) {
if (cc_ellipse_[i].Concentric(ellipse.center, cc_ellipse_range_)) {
std::cout<<"ellipse.center="<<ellipse.center<<std::endl;
cc_ellipse_[i].Add(ellipse);
return;
}
}
cc_ellipse_.push_back(ConcentricEllipse());
cc_ellipse_.back().Add(ellipse);
}
bool RecognizeMark(const ConcentricEllipse &cc_ellipse)
{//判断是不是满足要求的圆环,看大圆和小圆的大小有没有超出要求
const ConcentricEllipse::EllipseSet &ellipse_set = cc_ellipse.ellipse();
if (ellipse_set.size() < 2) return false;
ConcentricEllipse::CEllipseSetIterator itr = ellipse_set.begin();
float min_r = EllipseRadius(*itr);
itr = ellipse_set.end();
--itr;
float max_r = EllipseRadius(*itr);
float r_ratio = min_r / max_r;
if (r_ratio < mark_min_cc_r_ratio_ || r_ratio > mark_max_cc_r_ratio_) return false;
return true;
}
在这里解释以下,被注释掉的梯度滤波可以发现更多更全面的轮廓,但是我这里圆环的
轮廓明显,可以使用更粗糙的滤波方式,具体对比我这里就不展现了。
效果实现
输入图片
输出图片
总结
按照一般的处理过程处理直到识别出图像轮廓都没有问题,主要是按照自己的需求筛选出我们想要的轮廓,这就
要我们找轮廓特征然后结合合理的方法来筛选了。现在提供的代码只是部分,而且比较乱,之后会抽空整理一份完整的出来。