图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出感兴趣目标的技术和过程。它是由图像处理到图像分析的关键步骤。现有的图像分割方法主要分以下几类:基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法以及基于特定理论的分割方法等。从数学角度来看,图像分割是将数字图像划分成互不相交的区域的过程。图像分割的过程也是一个标记过程,即把属于同一区域的像素赋予相同的编号。
分水岭算法介绍,下面这位知乎博主已经讲得非常详细了,详情请自行查阅。
https://zhuanlan.zhihu.com/p/67741538
分水岭算法的整个过程:
过程动画用上面的算法对图像进行分水岭运算,由于噪声点或其它因素的干扰,可能会得到密密麻麻的小区域,即图像被分得太细(over-segmented,过度分割),这因为图像中有非常多的局部极小值点,每个点都会自成一个小区域。 其中的解决方法:
public static void watershed(Mat image, Mat markers)
findContours
和drawContours
从二值掩码中检索此类标记。标记是分水岭过程中的“种子”。标记图像中所有没有被标记的像素值为0。在输出图像中,两个区域之间的分割线用-1表示。/**
* 图像分割--分水岭法
* author: yidong
* 2020/11/9
*/
class WaterShedActivity : AppCompatActivity() {
private val mBinding: ActivityWaterShedBinding by lazy {
ActivityWaterShedBinding.inflate(
layoutInflater
)
}
private lateinit var mRgb: Mat
private lateinit var mGray: Mat
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mRgb = Mat()
mGray = Mat()
val bgr = Utils.loadResource(this, R.drawable.contourpoly)
Imgproc.cvtColor(bgr, mRgb, Imgproc.COLOR_BGR2RGB)
Imgproc.cvtColor(bgr, mGray, Imgproc.COLOR_BGR2GRAY)
mBinding.ivLena.showMat(mGray)
GlobalScope.launch(Dispatchers.IO) {
doWaterShed()
}
}
private fun doWaterShed() {
mBinding.progressBar.setVisible()
val markers = Mat(
mRgb.size(),
CvType.CV_32S,
Scalar.all(0.0)
)
// Imgproc.GaussianBlur(mGray, mGray, Size(13.0, 13.0), 4.0, 4.0
val binary = Mat()
Imgproc.threshold(
mGray,
binary,
20.0,
255.0,
Imgproc.THRESH_BINARY and Imgproc.THRESH_OTSU
)
mBinding.ivResult.showMat(binary)
val contours = mutableListOf<MatOfPoint>()
val hierarchy = Mat()
Imgproc.findContours(
binary,
contours,
hierarchy,
Imgproc.RETR_TREE,
Imgproc.CHAIN_APPROX_SIMPLE
)
for (i in 0 until contours.size) {
Imgproc.drawContours(
markers,
contours,
i,
Scalar.all(i + 1.toDouble()),
-1,
Imgproc.LINE_8,
hierarchy,
Int.MAX_VALUE
)
}
Imgproc.watershed(mRgb, markers)
val colors = mutableListOf<DoubleArray>()
for (k in 0 until contours.size) {
val r = Random.nextInt(0, 255)
val g = Random.nextInt(0, 255)
val b = Random.nextInt(0, 255)
val scalar = doubleArrayOf(r.toDouble(), g.toDouble(), b.toDouble())
colors.add(scalar)
}
val resultImg = Mat(mGray.size(), CvType.CV_8UC3)
for (i in 0 until markers.rows()) {
for (j in 0 until markers.cols()) {
val index = markers.get(i, j)[0].toInt()
if (index == -1) { // -1:区域之间的分割线用-1表示
resultImg.put(i, j, 255.0, 255.0, 255.0)
Log.d(App.TAG, " i= $i, j=$j")
} else if (index <= 0 || (index > contours.size)) { // <0 or >size:未标记区域
resultImg.put(i, j, 0.0, 0.0, 0.0)
} else { // 0,1,2,3... size-1: 标记区域
resultImg.put(
i,
j,
colors[index - 1][0],
colors[index - 1][1],
colors[index - 1][2]
)
}
}
}
mBinding.progressBar.setInvisible()
mBinding.ivResult.showMat(resultImg)
markers.release()
resultImg.release()
}
override fun onDestroy() {
mGray.release()
mRgb.release()
super.onDestroy()
}
}
彩色标记区域
分割线
https://github.com/onlyloveyd/LearningAndroidOpenCV