这是我参与更文挑战的第 7 天,活动详情查看: 更文挑战
轮廓面积与周长
轮廓面积和轮廓周长都是轮廓的重要统计特征。轮廓面积是指每个轮廓中所有像素点围成区域的面积,单位为像素。轮廓周长是指每个轮廓中所有像素点围成区域的周长,单位同样为像素。通过分析轮廓面积和轮廓周长,我们可以区分物体的大小,识别物体的不同,同时还能分析出一些其他内容,例如,正方形区域的周长和面积是有固定关系的,圆形区域的周长和面积是有固定关系的。通过计算轮廓面积和周长,再结合这些固定关系,我们是可以得到一些结论的。
API
轮廓面积
public static double contourArea(Mat contour, boolean oriented)
- 参数一:contour,轮廓的像素点。
- 参数二:oriented,区域面积是否有方向性的标志位。true表示有方向性,false表示不具有方向性。轮廓顶点顺时针轮廓面积和逆时针轮廓面积互为相反数。此参数默认为false。
轮廓周长
public static double arcLength(MatOfPoint2f curve, boolean closed)
- 参数一:curve,轮廓的像素点。
- 参数二:closed,轮廓是否闭合的标志位,true表示闭合,false表示不闭合。例如,计算三个点A,B,C的距离,若closed=true,则周长=AB+BC+CA,若closed=false,则周长=AB+BC。
操作
结合上篇《轮廓发现与绘制》示例,计算各轮廓的面积和周长。
/**
* 轮廓发现、绘制、面积、周长
* author: yidong
* 2020/9/19
*/
class FindContoursActivity : AppCompatActivity() {
private lateinit var mBinding: ActivityFindContoursBinding
private lateinit var mSource: Mat
private var level = 1
set(value) {
field = value
find()
mBinding.level.text = level.toString()
}
private var mFlag = Imgproc.RETR_TREE
set(value) {
field = value
find()
}
private var ignoreLevel = true
set(value) {
field = value
find()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_find_contours)
mBinding.ignoreLevel.setOnCheckedChangeListener { _, isChecked ->
ignoreLevel = isChecked
}
val bgr = Utils.loadResource(this, R.drawable.hierarchy)
mSource = Mat()
Imgproc.cvtColor(bgr, mSource, Imgproc.COLOR_BGR2RGB)
mBinding.ivLena.showMat(mSource)
title = "RETR_TREE"
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_contours, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
title = item.title
mFlag = when (item.itemId) {
R.id.retr_external -> Imgproc.RETR_EXTERNAL
R.id.retr_list -> Imgproc.RETR_LIST
R.id.retr_ccomp -> Imgproc.RETR_CCOMP
else -> Imgproc.RETR_TREE
}
return true
}
private fun find() {
val tmp = mSource.clone()
val gray = Mat()
Imgproc.cvtColor(mSource, gray, Imgproc.COLOR_BGR2GRAY)
Imgproc.GaussianBlur(gray, gray, Size(13.0, 13.0), 4.0, 4.0)
val binary = Mat()
Imgproc.threshold(gray, binary, 170.0, 255.0, Imgproc.THRESH_BINARY and Imgproc.THRESH_OTSU)
val contours = mutableListOf<MatOfPoint>()
val hierarchy = Mat()
Imgproc.findContours(
binary,
contours,
hierarchy,
mFlag,
Imgproc.CHAIN_APPROX_SIMPLE
)
for (i in 0 until contours.size) {
val area = Imgproc.contourArea(contours[i])
val source = MatOfPoint2f()
source.fromList(contours[i].toList())
val length = Imgproc.arcLength(source, true)
Log.d(App.TAG, "轮廓${i}面积:${area}; 周长:${length}")
}
if (ignoreLevel) {
Imgproc.drawContours(
tmp,
contours,
-1,
Scalar(255.0, 0.0, 0.0),
2,
Imgproc.LINE_AA
)
} else {
Imgproc.drawContours(
tmp,
contours,
-1,
Scalar(255.0, 0.0, 0.0),
2,
Imgproc.LINE_AA,
hierarchy,
level
)
}
mBinding.ivResult.showMat(tmp)
Log.d(App.TAG, "hierarchy: ${hierarchy.dump()}")
gray.release()
binary.release()
hierarchy.release()
tmp.release()
}
fun increase(v: View) {
level += 1
}
fun decrease(v: View) {
level -= 1
}
override fun onDestroy() {
mSource.release()
super.onDestroy()
}
}
效果
2020-10-02 13:57:57.801 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓0面积:471385.0; 周长:2967.313708305359
2020-10-02 13:57:57.801 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓1面积:434496.0; 周长:2861.9411249160767
2020-10-02 13:57:57.802 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓2面积:277124.0; 周长:2381.313708305359
2020-10-02 13:57:57.802 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓3面积:252129.0; 周长:2285.9411249160767
2020-10-02 13:57:57.802 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓4面积:25732.0; 周长:649.3137083053589
2020-10-02 13:57:57.802 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓5面积:48772.0; 周长:897.3137083053589
2020-10-02 13:57:57.802 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓6面积:10312.0; 周长:1401.313708305359
2020-10-02 13:57:57.802 6127-6127/cn.onlyloveyd.demo D/LearningAndroidOpenCV: 轮廓7面积:2588.0; 周长:489.3137083053589