Skip to content

霍夫圆变换

在本教程中,你将学到:

  • 如何使用 OpenCV 函数 HoughCircles() 来检测图像中的圆。

霍夫圆变换(Hough Circle Transform)

Section titled “霍夫圆变换(Hough Circle Transform)”

Hough_Circle_Tutorial_Theory_0.jpg

  • 霍夫圆变换的工作方式与上一篇教程中介绍的霍夫线变换(Hough Line Transform)大致类似。

  • 在直线检测的情形中,一条直线由两个参数 (r,θ)(r, \theta) 定义。而在圆的情形中,我们需要三个参数来定义一个圆:

    C:(xcenter,ycenter,r)C : ( x_{center}, y_{center}, r )

    其中 (xcenter,ycenter)(x_{center}, y_{center}) 定义圆心的位置(绿色点),rr 是半径。有了这三个参数就可以完全定义一个圆,如下图所示:

  • 出于效率考虑,OpenCV 实现了一种比标准霍夫变换稍微巧妙一些的检测方法:霍夫梯度法(Hough gradient method),它由两个主要阶段组成。第一阶段进行边缘检测并找出可能的圆心,第二阶段为每个候选圆心找出最合适的半径。更多细节请参阅 Learning OpenCV 一书或你喜欢的计算机视觉参考书。

  • 加载一幅图像并对其进行模糊处理以降低噪声;
  • 对模糊后的图像应用霍夫圆变换;
  • 在一个窗口中显示检测到的圆。

C++:

我们将要讲解的示例代码可以从 这里 下载。一个稍复杂一点的版本(带有用于调整阈值的 Trackbar)可以在 这里 找到。

/**
* @file houghcircles.cpp
* @brief This program demonstrates circle finding with the Hough transform
*/
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
const char* filename = argc >=2 ? argv[1] : "smarties.png";
// Loads an image
Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
// Check if image is loaded fine
if(src.empty()){
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default %s] \n", filename);
return EXIT_FAILURE;
}
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
medianBlur(gray, gray, 5);
vector<Vec3f> circles;
HoughCircles(gray, circles, HOUGH_GRADIENT, 1,
gray.rows/16, // change this value to detect circles with different distances to each other
100, 30, 1, 30 // change the last two parameters
// (min_radius & max_radius) to detect larger circles
);
for( size_t i = 0; i < circles.size(); i++ )
{
Vec3i c = circles[i];
Point center = Point(c[0], c[1]);
// circle center
circle( src, center, 1, Scalar(0,100,100), 3, LINE_AA);
// circle outline
int radius = c[2];
circle( src, center, radius, Scalar(255,0,255), 3, LINE_AA);
}
imshow("detected circles", src);
waitKey();
return EXIT_SUCCESS;
}

Java:

我们将要讲解的示例代码可以从 这里 下载。

package sample;
/**
* @file HoughCircles.java
* @brief This program demonstrates circle finding with the Hough transform
*/
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class HoughCirclesRun {
public void run(String[] args) {
String default_file = "../../../../data/smarties.png";
String filename = ((args.length > 0) ? args[0] : default_file);
// Load an image
Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_COLOR);
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image!");
System.out.println("Program Arguments: [image_name -- default "
+ default_file +"] \n");
System.exit(-1);
}
Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
Imgproc.medianBlur(gray, gray, 5);
Mat circles = new Mat();
Imgproc.HoughCircles(gray, circles, Imgproc.HOUGH_GRADIENT, 1.0,
(double)gray.rows()/16, // change this value to detect circles with different distances to each other
100.0, 30.0, 1, 30); // change the last two parameters
// (min_radius & max_radius) to detect larger circles
for (int x = 0; x < circles.cols(); x++) {
double[] c = circles.get(0, x);
Point center = new Point(Math.round(c[0]), Math.round(c[1]));
// circle center
Imgproc.circle(src, center, 1, new Scalar(0,100,100), 3, 8, 0 );
// circle outline
int radius = (int) Math.round(c[2]);
Imgproc.circle(src, center, radius, new Scalar(255,0,255), 3, 8, 0 );
}
HighGui.imshow("detected circles", src);
HighGui.waitKey();
System.exit(0);
}
}
public class HoughCircles {
public static void main(String[] args) {
// Load the native library.
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new HoughCirclesRun().run(args);
}
}

Python:

我们将要讲解的示例代码可以从 这里 下载。

import sys
import cv2 as cv
import numpy as np
def main(argv):
default_file = 'smarties.png'
filename = argv[0] if len(argv) > 0 else default_file
# Loads an image
src = cv.imread(cv.samples.findFile(filename), cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: hough_circle.py [image_name -- default ' + default_file + '] \n')
return -1
# Convert it to gray
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# Reduce the noise to avoid false circle detection
gray = cv.medianBlur(gray, 5)
rows = gray.shape[0]
circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, rows / 8,
param1=100, param2=30,
minRadius=1, maxRadius=30)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
# circle center
cv.circle(src, center, 1, (0, 100, 100), 3)
# circle outline
radius = i[2]
cv.circle(src, center, radius, (255, 0, 255), 3)
cv.imshow("detected circles", src)
cv.waitKey(0)
return 0
if __name__ == "__main__":
main(sys.argv[1:])

我们使用的测试图像可以在这里找到。

C++:

const char* filename = argc >=2 ? argv[1] : "smarties.png";
// Loads an image
Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
// Check if image is loaded fine
if(src.empty()){
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default %s] \n", filename);
return EXIT_FAILURE;
}

Java:

String default_file = "../../../../data/smarties.png";
String filename = ((args.length > 0) ? args[0] : default_file);
// Load an image
Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_COLOR);
// Check if image is loaded fine
if( src.empty() ) {
System.out.println("Error opening image!");
System.out.println("Program Arguments: [image_name -- default "
+ default_file +"] \n");
System.exit(-1);
}

Python:

default_file = 'smarties.png'
filename = argv[0] if len(argv) > 0 else default_file
# Loads an image
src = cv.imread(cv.samples.findFile(filename), cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: hough_circle.py [image_name -- default ' + default_file + '] \n')
return -1

C++:

Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);

Java:

Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

Python:

# Convert it to gray
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)

应用中值模糊以降低噪声、避免误检圆

Section titled “应用中值模糊以降低噪声、避免误检圆”

C++:

medianBlur(gray, gray, 5);

Java:

Imgproc.medianBlur(gray, gray, 5);

Python:

# Reduce the noise to avoid false circle detection
gray = cv.medianBlur(gray, 5)

C++:

vector<Vec3f> circles;
HoughCircles(gray, circles, HOUGH_GRADIENT, 1,
gray.rows/16, // change this value to detect circles with different distances to each other
100, 30, 1, 30 // change the last two parameters
// (min_radius & max_radius) to detect larger circles
);

Java:

Mat circles = new Mat();
Imgproc.HoughCircles(gray, circles, Imgproc.HOUGH_GRADIENT, 1.0,
(double)gray.rows()/16, // change this value to detect circles with different distances to each other
100.0, 30.0, 1, 30); // change the last two parameters
// (min_radius & max_radius) to detect larger circles

Python:

rows = gray.shape[0]
circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, rows / 8,
param1=100, param2=30,
minRadius=1, maxRadius=30)

各参数的含义:

  • gray:输入图像(灰度图)。
  • circles:一个向量,为每个检测到的圆存储三个值:xc,yc,rx_{c}, y_{c}, r。
  • HOUGH_GRADIENT:定义检测方法。目前 OpenCV 中只有这一种方法可用。
  • dp = 1:分辨率的反比。
  • min_dist = gray.rows/16:检测到的圆心之间的最小距离。
  • param_1 = 200:内部 Canny 边缘检测器的上阈值。
  • param_2 = 100:圆心检测的阈值。
  • min_radius = 0:要检测的最小半径。如果未知,默认设为 0。
  • max_radius = 0:要检测的最大半径。如果未知,默认设为 0。

C++:

for( size_t i = 0; i < circles.size(); i++ )
{
Vec3i c = circles[i];
Point center = Point(c[0], c[1]);
// circle center
circle( src, center, 1, Scalar(0,100,100), 3, LINE_AA);
// circle outline
int radius = c[2];
circle( src, center, radius, Scalar(255,0,255), 3, LINE_AA);
}

Java:

for (int x = 0; x < circles.cols(); x++) {
double[] c = circles.get(0, x);
Point center = new Point(Math.round(c[0]), Math.round(c[1]));
// circle center
Imgproc.circle(src, center, 1, new Scalar(0,100,100), 3, 8, 0 );
// circle outline
int radius = (int) Math.round(c[2]);
Imgproc.circle(src, center, radius, new Scalar(255,0,255), 3, 8, 0 );
}

Python:

if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
# circle center
cv.circle(src, center, 1, (0, 100, 100), 3)
# circle outline
radius = i[2]
cv.circle(src, center, radius, (255, 0, 255), 3)

可以看到,我们会把圆画成红色,并用一个小的绿点标出圆心。

显示检测到的圆并等待用户退出程序

Section titled “显示检测到的圆并等待用户退出程序”

C++:

imshow("detected circles", src);
waitKey();

Java:

HighGui.imshow("detected circles", src);
HighGui.waitKey();

Python:

cv.imshow("detected circles", src)
cv.waitKey(0)

Hough_Circle_Tutorial_Result.png

用一幅测试图像运行上面的代码,结果如下: