边缘特征检测

预备知识

梯度方向
亚像素坐标

基本边缘检测

在这里插入图片描述

Roberts算子

数学公式
在这里插入图片描述

检测模板
在这里插入图片描述

Prewitt算子

数学公式
在这里插入图片描述

检测模板
在这里插入图片描述
对角线方向边缘检测模板
在这里插入图片描述

sobel算子

较好的抑制(平滑)噪声

原理

数学公式
在这里插入图片描述

检测模板
在这里插入图片描述

对角线方向边缘检测模板
在这里插入图片描述

实验结果

rectangle
在这里插入图片描述

rectangle_smooth1
在这里插入图片描述

rectangle_smooth2
在这里插入图片描述
rectangle_smooth3
在这里插入图片描述
实验结果分析:
模糊程度会影响图像的梯度响应。模糊图像会减少图像的高频信息,也就是说,它会平滑图像的边缘和细节。这意味着在模糊图像中,像素强度的变化会更加平滑和缓慢,因此梯度响应(即像素强度变化的速度)通常会较小。
原图像梯度响应速度最快。rectangle_smooth1、rectangle_smooth2、rectangle_smooth3都有不同程度的减小

scharr算子

注意:当内核大小为三时,以上Sobel内核可能产生比较明显的误差,为了解决这个问题,我们使用Scharr函数,但该含函数只能作用与大小为3的内核。该函数的运算与Sobel一样快,但结果更加精确,其计算方法如下图:
在这里插入图片描述

绝对值近似梯度幅值(代价失去旋转不变)
在这里插入图片描述

canny边缘检测

原理与实现步骤

canny边缘检测算法是由4步构成

第一步:噪声去除
边缘检测容易受到噪声影响,首先使用高斯滤波器去除噪声
cv::Mat image;
	cv::GaussianBlur(src, image, cv::Size(3, 3), 1.5);
第二步:计算图像梯度

对平滑后的图像使用sobel算子计算水平方向和竖直方向的一阶导数(Gx,Gy)。根据这得到的这两幅梯度图(Gx,Gy)。计算边界梯度和方向
公式如下
在这里插入图片描述

//Step2. 使用sobel计算相应的梯度幅值及方向. Calculate gradient (apply sobel operator)
	cv::Mat magX, magY;//X,Y方向的梯度
	cv::Sobel(image, magX, CV_32FC1, 1, 0, 3);
	cv::Sobel(image, magY, CV_32FC1, 0, 1, 3);
	cv::Mat Mag, Ori;//梯度幅值,幅角
	cv::cartToPolar(magX, magY, Mag, Ori, true);//幅角0~360
第三步:非极大值抑制

梯度方向被归类为4类:垂直,水平和两个对角线方向。在获得梯度的方向和大小之后,对整幅图像进行扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中的最大值的。如下图所示:
在这里插入图片描述
A点位于图像的边缘,其梯度变化方向,选择像素点B和C,用来检验A点的梯度是否为极大值,是就保留,最终结果是有细边的二进制图像

//非极大值抑制
void nonMaximumSuppression(cv::Mat &magnitudeImage, const cv::Mat &directionImage)
{
	cv::Mat edgeMag_noMaxsup = cv::Mat::zeros(magnitudeImage.size(), CV_32FC1);


	//根据输入的角度,判断该点梯度方向位于位于那个区间
	//[0,45,90,135]
	auto _judgeDir = [](float angle)->int {

		if ((0 <= angle && angle < 22.5) || (157.5 <= angle && angle < 202.5) || (337.5 <= angle && angle < 360))//梯度方向为水平方向
			return 0;
		else if ((22.5 <= angle && angle < 67.5) || (202.5 <= angle && angle < 247.5))//45°方向
			return 45;
		else if ((67.5 <= angle && angle < 112.5) || ((247.5 <= angle && angle < 292.5)))
			return 90;
		else /*if( (112.5<=angle&&angle<157.5) || ((292.5<=angle&&angle<337.5)) )*/
			return 135;
	};

	for (int r = 1; r < magnitudeImage.rows - 1; ++r)
	{
		for (int c = 1; c < magnitudeImage.cols - 1; ++c)
		{
			const float mag = magnitudeImage.at<float>(r, c);//当前位置梯度幅值

			//将其量化到4个方向中进行计算
			const float angle = directionImage.at<float>(r, c);
			const int nDir = _judgeDir(angle);


			//非极大值抑制,8邻域的点进行比较,但只比较梯度方向
			//或者采用线性插值的方式,在亚像素层面进行比较
			//由于图像的y轴向下,x轴向右,因此注意这里的45°和135°
			switch (nDir)
			{
			case 0://梯度方向为水平方向-邻域内左右比较
			{
				float left = magnitudeImage.at<float>(r, c - 1);
				float right = magnitudeImage.at<float>(r, c + 1);
				if (mag > left && mag >= right)
					edgeMag_noMaxsup.at<float>(r, c) = mag;

				break;
			}
			case 135://即我们平常认为的45°.邻域内右上 左下比较.
			{
				float right_top = magnitudeImage.at<float>(r - 1, c + 1);
				float left_down = magnitudeImage.at<float>(r + 1, c - 1);
				if (mag > right_top && mag >= left_down)
					edgeMag_noMaxsup.at<float>(r, c) = mag;

				break;
			}
			case 90://梯度方向为垂直方向-邻域内上下比较
			{
				float top = magnitudeImage.at<float>(r - 1, c);
				float down = magnitudeImage.at<float>(r + 1, c);
				if (mag > top && mag >= down)
					edgeMag_noMaxsup.at<float>(r, c) = mag;

				break;
			}
			case 45://邻域内右下 左上比较
			{
				float left_top = magnitudeImage.at<float>(r - 1, c - 1);
				float right_down = magnitudeImage.at<float>(r + 1, c + 1);
				if (mag > left_top && mag >= right_down)
					edgeMag_noMaxsup.at<float>(r, c) = mag;

				break;
			}
			default:
				break;
			}//switch
		}//for col
	}//for row
第四步:双阈值检测和边缘检测

要确定真正的边界要设置两个阈值minVal和maxVal。
当图像的灰度高于maxVal时被认为是真正的边界(强边缘点)。
低于minVal的边界会被抛弃。
介于两个者之间,就看是否与某个确定为真正的边界点相连接,如果是就认为它也是边界点。
在这里插入图片描述
如上图所示,A高于阈值maxVal所以是真正的边界点,c介于两者之间,但与A相连,所以也认为是真正的边界点。B就会被抛弃。

//边缘链接
void followEdges(int x, int y, const cv::Mat &magnitude, int tUpper, int tLower, cv::Mat &edges)
{
	edges.at<uchar>(y, x) = 255;//该点与强边缘点邻接,故确定其为边缘点
	for (int i = -1; i < 2; i++)//8邻域: (i,j) ∈ [-1 0 1].一共8个点,因此要去掉自身
	{
		for (int j = -1; j < 2; j++)
		{
			if (i == 0 && j == 0)//去除自身点
				continue;

			// 边界限制
			if ((x + i >= 0) && (y + j >= 0) &&
				(x + i < magnitude.cols) && (y + j < magnitude.rows))
			{
				// 梯度幅值边缘判断及连接
				if ((magnitude.at<float>(y + j, x + i) > tLower)
					&& (edges.at<uchar>(y + j, x + i) != 255))//大于低阈值,且该点尚未被确定为边缘点
				{
					followEdges(x + i, y + j, magnitude, tUpper, tLower, edges);
				}
			}
		}
	}
}
//双阈值检测
void edgeDetect(const cv::Mat& magnitude, int tUpper, int tLower, cv::Mat& edges)
{
	int rows = magnitude.rows;
	int cols = magnitude.cols;
	edges = cv::Mat(magnitude.size(), CV_8UC1, cv::Scalar(0));

	for (int y = 0; y < rows; y++)
	{
		for (int x = 0; x < cols; x++)
		{
			// 梯度幅值判断.//大于高阈值,为确定边缘点
			if (magnitude.at<float>(y, x) >= tUpper)
			{
				followEdges(x, y, magnitude, tUpper, tLower, edges);
			}
		}
	}
}

实验结果

在这里插入图片描述

将两张图片合并分析
在这里插入图片描述
和cv自带canny在同样双阈值下存在差异

canny检测基础上的亚像素提取

用双线性内插法对边缘图像进行计算

代码

/******************************************双线性插值法*****************************************************/
// 双线性插值函数
double bilinearInterpolation(const cv::Mat& src, double y, double x)
{
	int x1 = floor(x);
	int y1 = floor(y);
	int x2 = ceil(x);
	int y2 = ceil(y);

	double q11 = src.at<uchar>(y1, x1);
	double q12 = src.at<uchar>(y2, x1);
	double q21 = src.at<uchar>(y1, x2);
	double q22 = src.at<uchar >(y2, x2);

	double r1 = ((x2 - x) / (x2 - x1)) * q11 + ((x - x1) / (x2 - x1)) * q21;
	double r2 = ((x2 - x) / (x2 - x1)) * q12 + ((x - x1) / (x2 - x1)) * q22;

	return ((y2 - y) / (y2 - y1)) * r1 + ((y - y1) / (y2 - y1)) * r2;
}

实验结果

在这里插入图片描述
边缘定位误差

在这里插入图片描述

hough变换

hough常用在提取图像中的直线和圆以及椭圆等几何形状

在这里插入图片描述

原理

笛卡尔坐标对应的霍夫变换

在笛卡尔坐标系中,一条直线是由两个点A=(x1,y1)和B确定
在这里插入图片描述
将直线y=kx+q可以写成关于(k,q)的函数表达式
在这里插入图片描述
对应的变换通过图形直观的表示如下:
在这里插入图片描述
变换之后的空间我们叫做霍夫空间。即:笛卡尔坐标中的一条直线可以表示为霍夫空间中的一个点。

A,B两个点·对应在霍夫空间中的情形:
在这里插入图片描述
下面是三点共线的情况
在这里插入图片描述
如果是在笛卡尔坐标系中的点共线,那么这些点在霍夫空间中对应的直线交于一点。
如果不止存在一条直线,如下图所示:
在这里插入图片描述
我们选择尽可能多的直线汇成的点

在这里插入图片描述

极坐标对应的霍夫空间

在这里插入图片描述
在这里插入图片描述
在极坐标下,极坐标的点对应霍夫空间的线,这时的霍夫空间不再是参数(k,q)的空间,而(ρ,)的空间,ρ是原点到直线的垂直距离, 表示直线的垂线与横轴顺时针方向的夹角,垂直线的角度为0度,水平线的角度是180度。
在这里插入图片描述
只要求的霍夫空间中的交点位置,即可以获得原坐标下的直线。

实现流程

假设有一个大小为100*100的图片,
设置累加器:初始化为0,ρ最大值为图片的对角线距离, 是角度的精度,最大可为180
在这里插入图片描述
1.取图片上的第一点(x,y),将其带入极坐标公式中,求出对应的 值,如果该数值在累加器上存在对应位置,则在该位置上加1
2.重复上述步骤,更新累加器中的值
3.搜索累加器中的最大,找出对应的(ρ,),就可以在图像中的直线表示出来

hough线检测

实现流程

参数:
img:检测的图像,要求是二值化图像,所以在进行霍夫变换之前首先进行二值化,或者进行canny边缘检测
rho、theta:ρ和 的精确度
threshold:阈值,只有累加器中的值高于该阈值时才能被认为是直线

代码实现

/*参数说明:
*src:待检测的原图像
*rho:以像素为单位的距离分辨率,即距离r离散时的单位长度
*theat:以角度为单位的距离分辨率,即角度Θ离散时的单位长度
*Threshold:累加器阈值,参数空间中离散化后每个方格被通过的
		   累计次数大于该阈值,则该方格代表的直线被视为在
		   原图像中存在
*lines:检测到的直线极坐标描述的系数数组,每条直线由两个参
	   数表示,分别为直线到原点的距离r和原点到直线的垂线与
	   x轴的夹角Θ
*/
//线检测
void myHoughLines(Mat &src, double rho, double theat, int Threshold, vector<Vec2f> &lines)
{
	if (src.empty() || rho < 0.1 || theat>360 || theat < 0)
		return;

	int row = src.rows;
	int col = src.cols;
	Mat gray;
	if (src.channels() > 1)
	{//灰度化
		cvtColor(src, gray, COLOR_BGR2GRAY);
	}
	else
		src.copyTo(gray);

	int maxDistance = sqrt(src.cols*src.cols + src.rows*src.rows);
	int houghMat_cols = 360 / theat;//霍夫变换后距离夹角坐标下对应的Mat的宽
	int houghMat_rows = maxDistance / rho;//霍夫坐标距离夹角下对应的Mat的高
	Mat houghMat = Mat::zeros(houghMat_rows, houghMat_cols, CV_32FC1);

	//边缘检测
	Canny(gray, gray, 100, 200, 3);

	//二值化
	threshold(gray, gray, 160, 255, THRESH_BINARY);

	//遍历二值化后的图像
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			if (gray.ptr<uchar>(i)[j] != 0)
			{
				/*从0到360度遍历角度,得到一组关于距离夹角的离散点,即得到
				一组关于经过当前点(i,j)按单位角度theat旋转得到的直线*/
				for (int k = 0; k < 360 / theat; k += theat)
				{
					double r = i * sin(k*CV_PI / 180) + j * cos(k*CV_PI / 180);
					if (r >= 0)
					{//直线到原点的距离必须大于0
						//获得在霍夫变换距离夹角坐标系下对应的Mat的行的下标
						int r_subscript = r / rho;

						//经过该直线的点数加1
						houghMat.at<float>(r_subscript, k) = houghMat.at<float>(r_subscript, k) + 1;
					}

				}
			}
		}
	}

	//经过直线的点数大于阈值,则视为在原图中存在该直线
	for (int i = 0; i < houghMat_rows; i++)
	{
		for (int j = 0; j < houghMat_cols; j++)
		{
			if (houghMat.ptr<float>(i)[j] > Threshold)
			{
				//line保存直线到原点的距离和直线到坐标原点的垂线和x轴的夹角
				Vec2f line(i*rho, j*theat*CV_PI / 180);
				lines.push_back(line);
			}
		}
	}

}
//画直线
void drawLine(Mat &img, vector<Vec2f> lines, double rows, double cols, Scalar scalar, int n)
{
	Point pt1, pt2;
	for (int i = 0; i < lines.size(); i++)
	{
		float rho = lines[i][0];//直线到坐标原点的距离
		float theat = lines[i][1];//直线到坐标原点的垂线和x轴的夹角
		double a = cos(theat);
		double b = sin(theat);
		double x0 = a * rho, y0 = b * rho;//直线与过坐标原点的垂线的交点
		double length = max(rows, cols);//突出高宽的最大值

		//计算直线上的一点
		pt1.x = cvRound(x0 + length * (-b));
		pt1.y = cvRound(y0 + length * (a));
		//计算直线上的另一点
		pt2.x = cvRound(x0 - length * (-b));
		pt2.y = cvRound(y0 - length * (a));
		while (pt1.x == pt2.x&&pt1.y == pt2.y)
		{
			//计算直线上的另一点
			pt2.x = cvRound(x0 + length * (-b));
			pt2.y = cvRound(y0 + length * (a));
		}
		//两点绘制直线
		line(img, pt1, pt2, scalar, n);
	}
}

实验结果

rho:以像素为单位的距离分辨率,即距离r离散时的单位长度 取值:1
theat:以角度为单位的距离分辨率,即角度Θ离散时的单位长度 取值:1
Threshold:累加器阈值,参数空间中离散化后每个方格被通过的 累计次数大于该阈值,则该方格代表的直线被视为在原图像中存在 取值:250
在这里插入图片描述

hough标准圆检测

三维空间投票,选择一个点为圆心投标,同一个圆心上的半径最多的点

hough梯度检测法

原理

代码实现

#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace std;
//*****************************************************圆检测********************************
//计算累加器和非零位置
static void _compute_accumulator(const cv::Mat& edges, const cv::Mat& dx, const cv::Mat& dy, float dp, int minRadius, int maxRadius, cv::Mat& accumulator, cv::Mat& nonZeroLocations) {
	assert(edges.type() == CV_8UC1);
	assert(dx.type() == CV_16SC1);
	assert(dy.type() == CV_16SC1);

	int accumulatorRows = cvCeil(edges.rows / dp);
	int accumulatorCols = cvCeil(edges.cols / dp);
	accumulator = cv::Mat::zeros(accumulatorRows + 2, accumulatorCols + 2, CV_32SC1);
	nonZeroLocations = cv::Mat::zeros(edges.size(), CV_8UC1);

	for (int y = 0; y < edges.rows; y++) {
		int x = 0;
		for (; x < edges.cols; x++) {
			float vx = dx.ptr<short>(y)[x];
			float vy = dy.ptr<short>(y)[x];
			if (edges.ptr<uchar>(y)[x] == 0 || (vx == 0 && vy == 0)) {
				continue;
			}
			float mag = std::sqrt(vx*vx + vy * vy);
			if (mag < 1.0f) {
				continue;
			}

			nonZeroLocations.ptr<uchar>(y)[x] = 1;

			int sx = cvRound(vx / dp * 1024 / mag);
			int sy = cvRound(vy / dp * 1024 / mag);
			int x0 = cvRound(x / dp * 1024);
			int y0 = cvRound(y / dp * 1024);

			for (int k = 0; k < 2; k++) {
				int x1 = x0 + minRadius * sx;
				int y1 = y0 + minRadius * sy;
				for (int r = minRadius; r <= maxRadius; x1 += sx, y1 += sy, r++) {
					int x2 = x1 >> 10;
					int y2 = y1 >> 10;
					if ((unsigned)x2 >= (unsigned)accumulatorCols || (unsigned)y2 >= (unsigned)accumulatorRows) {
						break;
					}
					accumulator.ptr<int>(y2)[x2]++;
				}
				sx = -sx;
				sy = -sy;
			}
		}
	}
}
//寻找圆心
static std::vector<cv::Point> _find_centers(const cv::Mat& accumulator, int accumulatorThresh) {
	std::vector<cv::Point> centers;
	for (int y = 1; y < accumulator.rows - 1; y++) {
		for (int x = 1; x < accumulator.cols - 1; x++) {
			if (accumulator.ptr<int>(y)[x] > accumulatorThresh &&
				accumulator.ptr<int>(y)[x] > accumulator.ptr<int>(y)[x - 1] &&
				accumulator.ptr<int>(y)[x] >= accumulator.ptr<int>(y)[x + 1] &&
				accumulator.ptr<int>(y)[x] > accumulator.ptr<int>(y - 1)[x] &&
				accumulator.ptr<int>(y)[x] >= accumulator.ptr<int>(y + 1)[x]) {
				centers.push_back(cv::Point(x, y));
			}
		}
	}
	return centers;
}
//自定义比较器,用于对圆心按照累加器值进行排序
struct compare_greater_than {
	compare_greater_than(const cv::Mat& _accumulator) {
		accumulator = _accumulator;
	}

	bool operator()(cv::Point pt1, cv::Point pt2) const {
		if (accumulator.ptr<int>(pt1.y)[pt1.x] > accumulator.ptr<int>(pt2.y)[pt2.x]) {
			return true;
		}
		if (accumulator.ptr<int>(pt1.y)[pt1.x] == accumulator.ptr<int>(pt2.y)[pt2.x]) {
			int n1 = pt1.y * accumulator.cols + pt1.x;
			int n2 = pt2.y * accumulator.cols + pt2.x;
			if (n1 < n2) {
				return true;
			}
		}
		return false;

	}

	cv::Mat accumulator;
};
//过滤非零点,筛选处于圆心半径范围内的点
static std::vector<float> _filter_circles(const cv::Point2f& currentCenter, const std::vector<cv::Point>& nonZeroPoints, int minRadius, int maxRadius) {

	float minRadius2 = (float)minRadius*minRadius;
	float maxRadius2 = (float)maxRadius*maxRadius;
	std::vector<float> radius;
	for (int i = 0; i < nonZeroPoints.size(); i++) {
		float dx = currentCenter.x - nonZeroPoints[i].x;
		float dy = currentCenter.y - nonZeroPoints[i].y;
		float r2 = dx * dx + dy * dy;
		if (r2 >= minRadius2 && r2 <= maxRadius2) {
			radius.push_back(r2);
		}
	}
	return radius;
}
//估算半径大小
static std::vector<cv::Vec4f> _estimate_radius(const std::vector<cv::Point>& centers, const std::vector<cv::Point>& nonZeroPoints, float dp, int accumulatorThresh, int minRadius, int maxRadius) {
	std::vector<cv::Vec4f> circles;

	const int nBinsPerDr = 10;
	int nBins = cvRound((maxRadius - minRadius) / dp * nBinsPerDr);
	std::vector<int> bins(nBins);

	for (int i = 0; i < centers.size(); i++) {

		int y = centers[i].y;
		int x = centers[i].x;

		cv::Point2f currentCenter = cv::Point2f((x + 0.5f)*dp, (y + 0.5f)*dp);
		std::vector<float> dist = _filter_circles(currentCenter, nonZeroPoints, minRadius, maxRadius);

		int numDist = dist.size();
		int maxCount = 0;
		float radiusBest = 0;
		if (numDist > 0) {

			std::vector<float> distSqrt;
			for (int j = 0; j < numDist; j++) {
				distSqrt.push_back(std::sqrt(dist[j]));
			}

			bins.assign(nBins, 0);
			for (int k = 0; k < numDist; k++) {
				int binIdx = cvRound((distSqrt[k] - minRadius) / dp * nBinsPerDr);
				binIdx = std::min(nBins - 1, binIdx);
				binIdx = std::max(0, binIdx);
				bins[binIdx]++;
			}

			int j = nBins - 1;
			while (j > 0) {

				if (bins[j] > 0) {
					int currentCount = 0;
					int k;
					for (k = j; k > j - nBinsPerDr && k >= 0; k--) {
						currentCount += bins[k];
					}
					float currentRadius = (j + k) / 2.f / nBinsPerDr * dp + minRadius;
					if ((currentCount * radiusBest >= maxCount * currentRadius) ||
						(radiusBest < FLT_EPSILON && currentCount >= maxCount)) {
						radiusBest = currentRadius;
						maxCount = currentCount;
					}
					j -= (nBinsPerDr + 1);
				}
				else {
					j--;
				}
			}
		}
		if (maxCount > accumulatorThresh) {
			circles.push_back(cv::Vec4f(currentCenter.x, currentCenter.y, radiusBest, maxCount));
		}
	}
	return circles;
}
//自定义比较器,用于对圆按照累加器值、半径和坐标进行排序
static bool _cmp_circles(const cv::Vec4f& c1, const cv::Vec4f& c2) {
	if (c1[3] > c2[3]) {
		return true;
	}
	else if (c1[3] < c2[3]) {
		return false;
	}
	else if (c1[2] > c2[2]) {
		return true;
	}
	else if (c1[2] < c2[2]) {
		return false;
	}
	else if (c1[0] < c2[0]) {
		return true;
	}
	else if (c1[0] > c2[0]) {
		return false;
	}
	else if (c1[1] < c2[1]) {
		return true;
	}
	else if (c1[1] > c2[1]) {
		return false;
	}
	else {
		return false;
	}
}

static void _remove_overlaps(std::vector<cv::Vec4f>& houghCircles, float minDist) {
	if (houghCircles.size() < 2) {
		return;
	}
	float minDist2 = minDist * minDist;
	int endIdx = 1;
	for (int i = 1; i < houghCircles.size(); i++) {
		cv::Vec4f circle = houghCircles[i];
		bool good_circle = true;
		for (int j = 0; j < endIdx; j++) {
			cv::Vec4f circle2 = houghCircles[j];
			float distx = circle[0] - circle2[0];
			float disty = circle[1] - circle2[1];
			if (distx * distx + disty * disty < minDist2) {
				good_circle = false;
				break;
			}
		}
		if (good_circle) {
			houghCircles[endIdx] = circle;
			endIdx++;
		}
	}
	houghCircles.resize(endIdx);
}

std::vector<cv::Vec4f> _hough_circles(const cv::Mat& binary, float dp, float minDist, double param1, double param2, int minRadius, int maxRadius) {
	assert(binary.type() == CV_8UC1);
	assert(dp > 0 && minDist > 0 && param1 > 0 && param2 > 0);

	std::vector<cv::Vec4f> houghCircles;

	int cannyThresh = cvRound(param1);
	int accumulatorThresh = cvRound(param2);
	int kernelSize = 3;

	minRadius = std::max(0, minRadius);
	if (maxRadius <= 0) {
		maxRadius = std::max(binary.rows, binary.cols);
	}
	else if (maxRadius <= minRadius) {
		maxRadius = minRadius + 2;
	}

	dp = std::max(dp, 1.f);

	cv::Mat edges, dx, dy;
	cv::Sobel(binary, dx, CV_16SC1, 1, 0, kernelSize, 1, 0, cv::BORDER_REPLICATE);
	cv::Sobel(binary, dy, CV_16SC1, 0, 1, kernelSize, 1, 0, cv::BORDER_REPLICATE);
	cv::Canny(dx, dy, edges, std::max(1, cannyThresh / 2), cannyThresh, false);

	cv::Mat accumulator, nonZeroLocations;
	_compute_accumulator(edges, dx, dy, dp, minRadius, maxRadius, accumulator, nonZeroLocations);
	std::vector<cv::Point> nonZeroPoints;
	cv::findNonZero(nonZeroLocations, nonZeroPoints);
	int nonZeroCount = nonZeroPoints.size();
	if (nonZeroCount < 1) {
		return houghCircles;
	}



	std::vector<cv::Point> centers = _find_centers(accumulator, accumulatorThresh);
	int centerCount = centers.size();
	if (centerCount < 1) {
		return houghCircles;
	}

	std::sort(centers.begin(), centers.end(), compare_greater_than(accumulator));

	houghCircles = _estimate_radius(centers, nonZeroPoints, dp, accumulatorThresh, minRadius, maxRadius);

	std::sort(houghCircles.begin(), houghCircles.end(), _cmp_circles);

	_remove_overlaps(houghCircles, minDist);

	return houghCircles;
}

static bool _is_same(const std::vector<cv::Vec4f>& c1, const std::vector<cv::Vec4f>& c2) {
	if (c1.size() != c2.size()) {
		return false;
	}
	for (int i = 0; i < c1.size(); i++) {
		if (std::fabs(c1[i][0] - c2[i][0]) > FLT_EPSILON ||
			std::fabs(c1[i][1] - c2[i][1]) > FLT_EPSILON ||
			std::fabs(c1[i][2] - c2[i][2]) > FLT_EPSILON ||
			(int)c1[i][3] != (int)c2[i][3]) {
			return false;
		}
	}
	return true;
}
int main(int argc, char **argv) {

	/***************************圆检测************************/
	cv::Mat src = cv::imread("c:/lib/image/circle.bmp", cv::IMREAD_COLOR);
	if (src.empty()) {
		std::cout << "failed to read image!" << std::endl;
		return EXIT_FAILURE;
	}
	cv::Mat gray;
	cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
	cv::Mat gb1, gb2;
	cv::GaussianBlur(gray, gb1, cv::Size(5, 5), 2, 2);
	gb2 = gb1.clone();

	int method = cv::HOUGH_GRADIENT;
	double dp = 1;
	double minDist = src.rows / 16;
	double param1 = 100;
	double param2 = 30;
	int minRadius = 1;
	int maxRadius = 100;

	std::vector<cv::Vec4f> houghCircles1;
	cv::HoughCircles(gb1, houghCircles1, method, dp, minDist, param1, param2, minRadius, maxRadius);
	cv::Mat dst = src.clone();
	for (int i = 0; i < houghCircles1.size(); i++) {
		cv::Vec4f c = houghCircles1[i];
		cv::circle(dst, cv::Point((int)c[0], (int)c[1]), (int)c[2], cv::Scalar(0, 0, 255), 3, cv::LINE_AA);
		cv::circle(dst, cv::Point((int)c[0], (int)c[1]), 2, cv::Scalar(0, 0, 255), 3, cv::LINE_AA);
	}

	std::vector<cv::Vec4f> houghCircles2 = _hough_circles(gb2, dp, minDist, param1, param2, minRadius, maxRadius);
	std::cout << houghCircles2.size() << std::endl;
	std::cout << (_is_same(houghCircles1, houghCircles2) ? "Result same" : "Result not same") << std::endl;

	imshow("src", src);
	imshow("dst", dst);
	waitkey(0);
    return 0;
}

实验结果

原图像
在这里插入图片描述
运行结果
在这里插入图片描述

瓶盖旋转

错误

实验结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值