二.圆形标记点的粗定位算法
标记点初定为模块采用了三种圆检测方法(Hough变换圆检测、斑点圆检测、基于分水岭算法园检测),对图像中的圆形标记点进行检测。该模块的功能为:实现标记点的粗定位,得到标记点圆心坐标;
2.1基于 Hough圆检测
Hough圆变换的基本思想:将图像从原图像空间变换到参数空间,在参数空间使用大多数边界点都满足的思想参数作为图像中的曲线描述,它通过设置累加器对参数进行累计,其峰值对应的点就是所需要的信息。
假设圆半径为r,圆心在处的目标一般圆方程是:
显然,由式子可知圆目标方程一共包括和r三个自由参数。
由Hough变换的原理可知,图像在0-XY坐标空间中的轮廓圆上的像素点都有着同样的特征参数,若将其变换至0—XYR三维的参数空间中,则轮廓圆上所有的像素点相应地转换为在三维参数空间中的所有圆锥面并相交于一点,而此点在参数坐标空间中的坐标即为轮廓圆的三个参数。二维轮廓圆图像的坐标空间转化至三维圆锥面参数的坐标空间,示意图如图所示:
OpenCV中的Hough变换圆检测函数-HoughCircles()
void HoughCircles( InputArray * image, InputArray circle,int method, double dp, double min_dist, double param1, double param2, int min_radius, int max_radius)
参数详解:
1.image:输入图像,原图像
2.circle:存储检测到的圆的输出矢量,每个矢量包含 (x, y, r) 这 3 个元素
3.method:使用的检测方法,一般都为 CV_HOUGH_GRADIENT
4.dp:输入图像分辨率与累加器图像的分辨率之比,若 dp=1 则具有相同的 分辨率 5.min_dist:霍夫变换检测到的圆的圆心之间的最小距离
6.param1:method设置的检测方法对应的参数,有默认值 100,表示传递给 canny 边缘检测算子的高阈值,低阈值是高阈值的一半 7.param2 method设置的检测方法对应的参数,有默认值 100,表示累加器 的阈值 8.min_radius:有默认值 0 表示圆半径的最小值
9.max_radius:有默认值 0 表示圆半径的最大值
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
Mat g_scrImage,g_scrImagecopy, g_dstImage, g_midImage;
vector<Vec3f>circles;
int g_nthreshold = 15;
static void on_HoughCircles(int, void*);
void showText();
int main(){
g_scrImage = imread("C:\\Users\\26011\\Desktop\\毕设\\毕设程序\\picture\\1.bmp");
g_scrImagecopy = imread("C:\\Users\\26011\\Desktop\\毕设\\毕设程序\\picture\\1.bmp");
on_HoughCircles(1.5, 0);
while (char(waitKey()) != 'q'){}
return 0;
}
static void on_HoughCircles(int, void*){
cvtColor(g_scrImage, g_midImage, CV_BGR2GRAY);
threshold(g_midImage,g_midImage, 100, 255, CV_THRESH_BINARY_INV);//图像二值化
GaussianBlur(g_midImage, g_midImage, Size(9,9), 2, 2);//必须要用高斯blur
double g_nthresholdcopy = (double)g_nthreshold / 10;
HoughCircles( g_midImage, circles, CV_HOUGH_GRADIENT, g_nthresholdcopy, g_midImage.rows/20,88,40, 8, 24 );//输入图像为灰度图
/*第四个参数设置累加器分辨率,第五个参数,最小圆心距;第六个参数,默认值为100,传给canny的阈值;
第七个参数,100,小检测更多的圆,大圆形完美;第八/9个参数,0,圆半径最小/大值*/
for (size_t i = 0; i < circles.size(); i++){
//提取出圆心坐标
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
//提取出圆半径
int radius = cvRound(circles[i][2]);
//圆心
circle( g_scrImage, center, 3, Scalar(0,0,0), -1, 8, 0 );
//圆
circle( g_scrImage, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
imshow("效果图", g_scrImage);
g_scrImage = g_scrImagecopy.clone();
}
2.2基于斑点圆检测
SimpleBlobDetector入门
SimpleBlobDetector源码分析
算法的实现
1.阈值处理:通过以minThreshold开始的阈值对源图像进行阈值处理,将源图像转换为多个二进制图像 。这些阈值以thresholdStep递增, 直到 maxThreshold。因此,第一个阈值为 minThreshold, 第二个阈值为 minThreshold + thresholdStep,第三个阈 值为 minThreshold + 2 x thresholdStep,依此类推。
2.分组: 在每个二进制图像中,连接的白色像素被分组在一起。让我们称这些二进制blob。
3.合并 :计算二进制图像中二进制斑点的中心,并合并比minDistBetweenBlob 更近的斑点 。
4.中心和半径计算: 计算并返回新合并的Blob的中心和半径。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/calib3d/calib3d.hpp>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
vector<KeyPoint>detectKeyPoint;
Mat keyPointImage1, keyPointImage2;
Mat srcGrayImage;
int main(int argc, char** argv)
{
//Mat srcImage = imread("C:\\Users\\26011\\Desktop\\毕设\\毕设程序\\picture\\1.bmp");
Mat srcImage = imread("C:\\Users\\26011\\Desktop\\毕设\\毕设程序\\picture\\2.bmp");
//Mat srcImage = imread("C:\\Users\\26011\\Desktop\\毕设\\毕设程序\\picture\\3.bmp");
resize(srcImage, srcImage, Size(), 0.6, 0.6, 1);
imshow("原图", srcImage);
cvtColor(srcImage, srcGrayImage, CV_BGR2GRAY);//灰度化
threshold(srcGrayImage,srcGrayImage, 100, 255, CV_THRESH_BINARY_INV);//图像二值化
//imshow("灰度化", srcGrayImage);
//imshow("二值化", srcGrayImage);
//高斯模糊平滑
//GaussianBlur(srcGrayImage, srcGrayImage, Size(9, 9), 0, BORDER_DEFAULT);
//imshow("高斯降噪", srcGrayImage);
SimpleBlobDetector::Params params;
params.minArea = 100;//斑点的最小面积
params.maxArea = 5000;//斑点的最大面积
params.filterByCircularity = true;//斑点圆度的限制
Ptr<SimpleBlobDetector> sbd = SimpleBlobDetector::create(params);
sbd->detect(srcGrayImage, detectKeyPoint);
drawKeypoints(srcImage, detectKeyPoint, keyPointImage2, Scalar(0, 0, 255), DrawMatchesFlags::DRAW_RICH_KEYPOINTS); //圆轮廓
imshow("圆轮廓", keyPointImage2);
int Npoint = (int)detectKeyPoint.size();
//画出圆心
for (size_t i = 0; i < Npoint; i++)
{
circle(keyPointImage2, Point2f(detectKeyPoint[i].pt.x, detectKeyPoint[i].pt.y), 1, Scalar(0, 0, 0), -1, 8, 0);
}
namedWindow("keyPoint image2", CV_WINDOW_NORMAL);
imshow("keyPoint image2", keyPointImage2);
//imwrite("d", keyPointImage1);
waitKey(0);
return 0;
}
2.3基于分水岭算法圆检测
简介
watershed自动图像分割用法
处理流程:
1.用filter2D和拉普拉斯算子实现图像对比度提高(锐化增强图像);
2.转换为二值图像(threshold); //图像预处理
3.距离变换
4.对距离变换结果进行归一化到[0~1]之间(归一化提高计算精度)
5.使用阈值,再次二值化,得到标记
6.腐蚀得到每个peak-erode(使目标区域范围缩小,造成图像的边界收缩,消除小且无意义的目标物)
7.发现轮廓-findContours
8.绘制轮廓-drawContours/circle
9.分水岭变换-watershed
10.canny算子提取圆轮廓
//#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
int main(int argc, char*argv)
{
Mat src;
src = imread("C:\\Users\\26011\\Desktop\\毕设\\毕设程序\\picture\\2.bmp");
//resize(src, src, Size(), 0.6, 0.6, 1);
if (!src.data)
{
printf("could not load image...\n");
return -1;
}
namedWindow("input", CV_WINDOW_AUTOSIZE);
imshow("input", src);
//将白色背景变成黑色-目的是为后面的变换做准备
/*for (int row = 0; row < src.rows; row++)
{
for (int col = 0; col<src.cols; col++)
{
if (src.at<Vec3b>(row, col)[0]>200 && src.at<Vec3b>(row, col)[1]>200 && src.at<Vec3b>(row, col)[2]>200)
{
src.at<Vec3b>(row, col)[0] = 0;
src.at<Vec3b>(row, col)[1] = 0;
src.at<Vec3b>(row, col)[2] = 0;
}
}
}
const char*input = "change backgroud image";
namedWindow(input, CV_WINDOW_AUTOSIZE);
imshow(input, src);*/
//使用filter2D与拉普拉斯算子实现图像对比度提高,sharp
Mat imgLaplance;
Mat sharp = src;
Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
filter2D(sharp, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
src.convertTo(sharp, CV_32F);
Mat imgResult = sharp - imgLaplance;
//显示
imgResult.convertTo(imgResult, CV_8UC3);
imgLaplance.convertTo(imgLaplance, CV_8UC3);
//namedWindow("Sharp image", CV_WINDOW_AUTOSIZE);
//imshow("Sharp image", imgResult);
// src = resultImg; // copy back
//binary image转为二值图像通过threshold
Mat binaryImag;
cvtColor(src, binaryImag, CV_BGR2GRAY);
threshold(binaryImag, binaryImag, 40, 255, THRESH_OTSU | THRESH_BINARY);
//namedWindow("binary image", CV_WINDOW_AUTOSIZE);
//imshow("binary image", binaryImag);
//距离变换(distance transform )
Mat distImg;
distanceTransform(binaryImag, distImg, DIST_L1, 3, 5);
//cv::distanceTransform(InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP)
//distanceType = DIST_L1 / DIST_L2,
//maskSize = 3x3, 最新的支持5x5,推荐3x3、
//labels离散维诺图输出
//dst输出8位或者32位的浮点数,单一通道,大小与输入图像一致
//对距离变换结果进行归一化到[0~1]之间
normalize(distImg, distImg, 0, 1, NORM_MINMAX);
//imshow("distance image", distImg);
//使用阈值,再次二值化,得到标记(binary again)
threshold(distImg, distImg, .2, 1, THRESH_BINARY);
//腐蚀得到每个Peak - erode
Mat kernel1 = Mat::ones(13, 13, CV_8UC1);
erode(distImg, distImg, kernel1, Point(-1, -1));
//imshow("distance binary image", distImg);
//查找轮廓
Mat dist_8u;
distImg.convertTo(dist_8u, CV_8U);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(dist_8u,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
Mat imageContours=Mat::zeros(src.size(),CV_8UC1); //轮廓
Mat marks(src.size(),CV_32S); //Opencv分水岭第二个矩阵参数
marks=Scalar::all(0);
int index = 0;
int compCount = 0;
for( ; index >= 0; index = hierarchy[index][0], compCount++ )
{
//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点
drawContours(marks, contours, index, Scalar::all(compCount+1), 1, 8, hierarchy);
drawContours(imageContours,contours,index,Scalar(255),1,8,hierarchy);
circle(marks, Point(12, 12), 5, Scalar(255, 255, 255), -1); //画圆:原图,圆心,半径,线颜色,thickness(正值代表圆线宽,负值代表填充圆)
}
//我们来看一下传入的矩阵marks里是什么东西
Mat marksShows;
convertScaleAbs(marks,marksShows);
//imshow("marksShow",marksShows);
//imshow("轮廓",imageContours);
//watershed(src,marks);
//我们再来看一下分水岭算法之后的矩阵marks里是什么东西
//Mat afterWatershed;
//convertScaleAbs(marks,afterWatershed);
//imshow("After Watershed",afterWatershed);
// perform watershed(基于浸泡理论实现 )
watershed(src, marks);
Mat mark = Mat::zeros(marks.size(), CV_8UC1);
marks.convertTo(mark, CV_8UC1);
//src.convertTo(dst, type, scale, shift)
//dst:目的矩阵;
//type:需要的输出矩阵类型,或者更明确的,是输出矩阵的深度,如果是负值(常用 - 1)则输出矩阵和输入矩阵类型相同;
//scale : 比例因子;
//shift:将输入数组元素按比例缩放后添加的值;
bitwise_not(mark, mark, Mat());
resize(mark, mark, Size(), 0.6, 0.6, 1);
imshow("watershed image", mark);
Canny(mark,mark,80,150);
imshow("Canny Image",mark);
// generate random color
/*vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++) {
int r = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int b = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// fill with color and display final result
Mat dst = Mat::zeros(marks.size(), CV_8UC3);
for (int row = 0; row < marks.rows; row++) {
for (int col = 0; col < marks.cols; col++) {
int index = marks.at<int>(row, col);
if (index > 0 && index <= static_cast<int>(contours.size())) {
dst.at<Vec3b>(row, col) = colors[index - 1];
}
else {
dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
}
}
}
resize(dst, dst, Size(), 0.3, 0.3, 1);
imshow("Final Result", dst);*/
waitKey(0);
return 0;
}