标准霍夫变换的原理就是把图像空间转换成参数空间(即霍夫空间),例如霍夫变换的直线检测就是在距离 -角度空间内进行检测。圆可以表示为:
(
x
−
a
)
2
+
(
y
−
b
)
2
=
r
2
(x-a)^2+(y-b)^2 = r^2
(x−a)2+(y−b)2=r2 其中a和b代表圆心坐标,r代表圆半径。因此,霍夫变换的圆检测就是在这三个参数组成的三维空间内进行。原则上,霍夫变换可以检测任何形状。但复杂的形状需要的参数很多,霍夫空间的维数对应就多,因此在程序实现上所需的内存空间以及运行效率上都不利于把标准霍夫变换应用于实际复杂图形的检测中。 所以一些改进的霍夫变换就相继提出,它们的基本原理就是尽可能减小霍夫空间的维数。
霍夫圆检测
霍夫圆检测分为两个阶段:
检测圆心从圆心推导出圆半径
检测圆心
检测圆心的原理是圆心是它所在圆周所有法线的交点。因此只要找到法线的交点,即可确定圆心。具体步骤如下:
边缘检测;计算图像梯度,并确定圆周线。圆周线的梯度即为法线;在二维霍夫空间内,绘制所有图形的梯度直线,某坐标点上累加和的值越大,说明在该点上直线相交的次数越多,也就越可能是圆心;在霍夫空间内,4领域内进行非最大值抑制;设定阈值,霍夫空间内累加和大于该阈值的点就对应于圆心。
从圆心推导出圆半径
计算某一个圆心到所有圆周线的距离;设定两个阈值,定义为最大半径和最小半径,保留距离在这两个半径之间的值,这意味着我们检测的圆不能太大,也不能太小;对保留下来的距离进行排序;找到距离相同的那些值,并计算相同值的数量;设定一个阈值,只有相同值的数量大于该阈值,才认为该值是该圆心对应的圆半径;对每一个圆心,完成以上步骤,得到所有的圆半径。
API
public static void
HoughCircles(Mat image
, Mat circles
, int method
, double dp
, double minDist
, double param1
, double param2
, int minRadius
, int maxRadius
)
参数一:image,待检测圆形的图像,必须是CV_8UC1的灰度图
参数二:circles,检测结果。每个圆形用3个参数表示,即(x , y , radius),圆心坐标和圆半径
参数三:method,检测圆形的方法标志,虽然有4个可选项,但是目前只实现了HOUGH_GRADIENT
public static final int
HOUGH_STANDARD
= 0,
HOUGH_PROBABILISTIC
= 1,
HOUGH_MULTI_SCALE
= 2,
HOUGH_GRADIENT
= 3;
参数四:dp,累加器分辨率与图像分辨率的反比。例如,如果 dp = 1,则累加器具有与输入图像相同的分辨率。如果dp = 2,则累加器的宽度和高度为输入图像一半
参数五:minDist,检测结果中两个圆心之间的最小距离。如果参数太小,则除了真实的圆圈外,还可能会错误地检测到多个邻居圆圈。 如果太大,可能会错过一些圆圈。
参数六:param1,使用HOUGH_GRADIENT检测圆形时,它是传递给Canny边缘检测器的两个阈值中的较大值(较小值为较大值的一半)。
参数七:param2,使用HOUGH_GRADIENT检测圆形时,此参数为检测圆形的累加器阈值,阈值越大,则检测的圆形越精准。
参数八:minRadius,检测圆的最小半径。
参数九:minRadius,检测圆的最大半径。
操作
class HoughCircleDetectActivity
: AppCompatActivity() {
private lateinit var mBinding
: ActivityHoughCircleBinding
private lateinit var mGray
: Mat
private lateinit var mRgb
: Mat
override fun onCreate(savedInstanceState
: Bundle
?) {
super.onCreate(savedInstanceState
)
mBinding
= DataBindingUtil
.setContentView(this, R
.layout
.activity_hough_circle
)
mBinding
.presenter
= this
mGray
= Mat()
mRgb
= Mat()
val source
= Utils
.loadResource(this, R
.drawable
.coins
)
Imgproc
.cvtColor(source
, mGray
, Imgproc
.COLOR_BGR2GRAY
)
Imgproc
.cvtColor(source
, mRgb
, Imgproc
.COLOR_BGR2RGB
)
mBinding
.ivLena
.showMat(mRgb
)
source
.release()
}
fun doHoughCircleDetect() {
val circle
= Mat()
Imgproc
.GaussianBlur(mGray
, mGray
, Size(9.0, 9.0), 2.0, 2.0)
Imgproc
.HoughCircles(
mGray
,
circle
,
Imgproc
.HOUGH_GRADIENT
,
2.0,
240.0,
100.0,
100.0,
100,
200
)
for (index
in 0 until circle
.cols()) {
val content
= FloatArray(3)
circle
.get(0, index
, content
)
val center
=
Point(content
[0].roundToInt().toDouble(), content
[1].roundToInt().toDouble())
val radius
= content
[2].roundToInt()
Imgproc
.circle(mRgb
, center
, 3, Scalar(0.0, 255.0, 0.0), -1, 8, 0)
Imgproc
.circle(mRgb
, center
, radius
, Scalar(0.0, 0.0, 255.0), 3, 8, 0)
mBinding
.ivResult
.showMat(mRgb
)
}
}
override fun onDestroy() {
mGray
.release()
mRgb
.release()
super.onDestroy()
}
}
效果
源码
https://github.com/onlyloveyd/LearningAndroidOpenCV
扫码关注,持续更新
回复【计算机视觉】【Android】【Flutter】【数字图像处理】获取对应学习资料。
onlyloveyd
认证博客专家
Android
Kotlin
OpenCV
个人公众号【OpenCV or Android】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注Android、OpenCV、Kotlin、Flutter等有趣的技术。