图像轮廓是一系列相连的点组成的曲线,代表物体的基本外形。轮廓与边缘的区别在于,轮廓是连续的,边缘并不全部连续。
轮廓发现的操作一般用于二值化图,所以通常会使用阈值分割或Canny边缘检测先得到二值图。
注意,轮廓发现是针对白色物体的,一定要保证物体是白色,而背景是黑色,不然很多人在寻找轮廓时会找到图片最外面的一个框。
轮廓层级
部分内容翻译自:https://docs.opencv.org/3.1.0/d9/d8b/tutorial_py_contours_hierarchy.html
通常我们使用findContours()
函数寻找图像中的轮廓。某些情况下,某些形状在其他形状内,就像嵌套的数字一样。 在这种情况下,我们将外部形状称为父项
,将内部形状称为子项
。 这样,图像中的轮廓彼此之间就可以建立起关系。此关系的表示称为层次结构
。如下图所示:
图中,编号0,1,2,2a,3,3a,4,5。其中轮廓0,1,2是最外部轮廓。 它们在第0层,同时他们属于同一层级。接下来是轮廓-2a, 它是轮廓-2的子项(或换言之,轮廓-2是轮廓-2a的父项),它们属于第一层。以此类推,轮廓-3是轮廓-2的子项,它属于下一层。 最后,轮廓4,5是轮廓-3a的子项,它们位于最后一层。 从编号框方式来看,轮廓-4是轮廓-3a的第一个子项(其实也可以是轮廓-5)。
层次结构表示
每个轮廓都包含其层次结构信息,谁是其子级,谁是其父级等的信息。OpenCV用四个值的数组表示这种层次信息:[Next,Previous,First_Child,Parent]
-
Next:同层下一个轮廓索引
图中看轮廓-0。 轮廓-1为其同一水平的下一个轮廓,所以Next = 1。类似地,对于轮廓-1,同一水平的下一个轮廓是轮廓-2, 所以Next = 2。
-
Previous:同层上一个轮廓索引
图中看轮廓-1。轮廓-0为其同一水平的上一个轮廓,所以Previous = 0。类似地,对于轮廓-2,Previous = 1。 而对于轮廓-0,同一水平没有上一个轮廓,所以Previsou = -1。
-
First_Child:下层第一个子轮廓索引
图中看轮廓-2。子项为轮廓-2a。因此First_Child=轮廓-2a索引值。针对如轮廓-3a这种存在多个子项的,我们只取第一个子项,也就是轮廓-4。针对没有子项的轮廓,我们取-1。
-
Parent:父轮廓
图中看轮廓-4,其父项为轮廓-3a,则Parent = 轮廓3a索引值。针对如轮廓-0这类没有父项的轮廓,Parent = -1。
轮廓检索模式
RETR_EXTERNAL
此标记位只检索最高层级的轮廓。针对上图,应该只有3个轮廓,轮廓0,1,2。关系数组为:
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])
RETR_LIST
四个标志位中最简单的一个(从解释的角度来看)。它只是检索所有的轮廓,但不创建任何父子关系。所有轮廓都属于同一层次。所以,层次结构表示数组中,第三个元素和第四个元素都是-1。针对上图,如果采用此标志位寻找轮廓,得到的关系数组为:
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[ 3, 1, -1, -1],
[ 4, 2, -1, -1],
[ 5, 3, -1, -1],
[ 6, 4, -1, -1],
[ 7, 5, -1, -1],
[-1, 6, -1, -1]]])
RETR_CCOMP
此标志位检索所有轮廓并将它们排列为2级层次结构。形状的外部轮廓(物体的边界)被置于层级-1中。对象内部的孔(如果有)的轮廓放置在层级-2中。如果其中还有任何对象,则其轮廓将再次仅放置在层级-1中,它在内部的孔放置至层级-2中,以此类推等等。下图很好的诠释了这个概念。红色字母给出轮廓编号,绿色字母给出层次结构顺序。
RETR_TREE
此标记位检索所有轮廓,并建立完成的层次关系。下图很好的诠释了这个概念。红色字母给出轮廓编号,绿色字母给出层次结构顺序。
API
public static void findContours(Mat image, List<MatOfPoint> contours, Mat hierarchy, int mode, int method, Point offset)
-
参数一:image,输入图像,数据类型为
CV_8U
的单通道灰度图像或者二值化图像。如果mode等于RETR_CCOMP
或RETR_FLOODFILL
,则输入图像类型也可以为CV_32SC1
-
参数二:contours,检测到的轮廓。每个轮廓中存放着像素的坐标。
-
参数三:hierarchy,轮廓结构关系描述向量。
-
参数四:mode,轮廓检测模式标志位。
// C++: enum RetrievalModes public static final int RETR_EXTERNAL = 0, // 只检测最外层轮廓,对所有轮廓设置hierarchy[i][2]=hierarchy[i][3]=-1 RETR_LIST = 1, // 提取所有轮廓,并且放置在list中。检测的轮廓不建立等级关系。也就是所有轮廓属于同一层级 RETR_CCOMP = 2, // 提取所有轮廓,并且将其组织为双层结构。顶层为连通域的外围边界,次层为孔的内层边界。也就是所有轮廓分2个层级,不是外界就是最里层 RETR_TREE = 3, // 提取所有轮廓,并建立完整的层次关系 RETR_FLOODFILL = 4; // 官网竟然没有解释
-
参数五:method,轮廓逼近方法标志位。
// C++: enum ContourApproximationModes public static final int CHAIN_APPROX_NONE = 1, // 获取每个轮廓的每个像素点,相邻的两个点的像素位置相差为1,也就是要么水平相邻,要么垂直相邻 CHAIN_APPROX_SIMPLE = 2, // 压缩水平,垂直和对角线段,仅保留其端点。 例如,一个直立的矩形轮廓编码有4个点。 CHAIN_APPROX_TC89_L1 = 3, // 使用Teh-Chin链逼近算法的一种 CHAIN_APPROX_TC89_KCOS = 4;// 使用Teh-Chin链逼近算法的一种
-
参数六:offset,每个轮廓点移动的可选偏移量。如果从图像ROI中提取轮廓,然后在整个图像上下文中对其进行分析,这个参数才会被使用到。
public static void drawContours(Mat image, List<MatOfPoint> contours, int contourIdx, Scalar color, int thickness, int lineType, Mat hierarchy, int maxLevel, Point offset)
-
参数一:image,待绘制轮廓的图像。
-
参数二:contours,待绘制的轮廓集合。
-
参数三:contourIdx,要绘制的轮廓在contours中的索引,若为负数,表示绘制全部轮廓。
-
参数四:color,绘制轮廓的颜色。
-
参数五:thickness,绘制轮廓的线条粗细。若为负数,那么绘制轮廓的内部。
-
参数六:lineType,线条类型。
// C++: enum LineTypes public static final int FILLED = -1, LINE_4 = 4, // 4连通 LINE_8 = 8, // 8连通 LINE_AA = 16; // 抗锯齿
-
参数七:hierarchy,可选的结构关系。只有在绘制部分轮廓时,此参数才会使用到。
-
参数八:maxLevel,绘制轮廓的最大等级。
-
参数九:offset,轮廓检测模式标志位。
操作
/**
* 发现轮廓
* 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
)
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()
}
}
效果
层次关系
示例中各种标记位对应输出的层次关系
RETR_EXTERNAL
[1, -1, -1, -1,
2, 0, -1, -1,
-1, 1, -1, -1]
RETR_LIST
[1, -1, -1, -1,
2, 0, -1, -1,
3, 1, -1, -1,
4, 2, -1, -1,
5, 3, -1, -1,
6, 4, -1, -1,
7, 5, -1, -1,
-1, 6, -1, -1]
RETR_CCOMP
[1, -1, -1, -1,
2, 0, -1, -1,
4, 1, 3, -1,
-1, -1, -1, 2,
6, 2, 5, -1,
-1, -1, -1, 4,
7, 4, -1, -1,
-1, 6, -1, -1]
RETR_TREE
[6, -1, 1, -1,
-1, -1, 2, 0,
-1, -1, 3, 1,
-1, -1, 4, 2,
5, -1, -1, 3,
-1, 4, -1, 3,
7, 0, -1, -1,
-1, 6, -1, -1]
源码
https://github.com/onlyloveyd/LearningAndroidOpenCV
扫码关注,持续更新
扫码关注并回复下列关键字,获取对应学习资料。
- 【计算机视觉】
- 【Android】
- 【Flutter】
- 【数字图像处理】
- 【计算机视觉算法与应用】