预备知识
梯度方向
亚像素坐标
基本边缘检测
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;
}
实验结果
原图像
运行结果
瓶盖旋转
错误
实验结果