kul*_*der 3 python java android opencv
我想在 Android 中使用 Open CV 删除图像背景。代码工作正常,但输出质量不符合预期。我按照java文档获取代码参考:
https://opencv-java-tutorials.readthedocs.io/en/latest/07-image-segmentation.html
谢谢
我在Android中的代码片段:
private fun doBackgroundRemoval(frame: Mat): Mat? {
// init
val hsvImg = Mat()
val hsvPlanes: List<Mat> = ArrayList()
val thresholdImg = Mat()
var thresh_type = Imgproc.THRESH_BINARY_INV
thresh_type = Imgproc.THRESH_BINARY
// threshold the image with the average hue value
hsvImg.create(frame.size(), CvType.CV_8U)
Imgproc.cvtColor(frame, hsvImg, Imgproc.COLOR_BGR2HSV)
Core.split(hsvImg, hsvPlanes)
// get the average hue value of the image
val threshValue: Double = getHistAverage(hsvImg, hsvPlanes[0])
threshold(hsvPlanes[0], thresholdImg, threshValue, 78.0, thresh_type)
Imgproc.blur(thresholdImg, thresholdImg, Size(1.toDouble(), 1.toDouble()))
val kernel1 =
Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, Size(11.toDouble(), 11.toDouble()))
val kernel2 = Mat.ones(3, 3, CvType.CV_8U)
// dilate to fill gaps, erode to smooth edges
Imgproc.dilate(thresholdImg, thresholdImg, kernel1, Point(-1.toDouble(), -1.toDouble()), 1)
Imgproc.erode(thresholdImg, thresholdImg, kernel2, Point(-1.toDouble(), -1.toDouble()), 7)
threshold(thresholdImg, thresholdImg, threshValue, 255.0, Imgproc.THRESH_BINARY_INV)
// create the new image
val foreground = Mat(
frame.size(), CvType.CV_8UC3, Scalar(
255.toDouble(),
255.toDouble(),
255.toDouble()
)
)
frame.copyTo(foreground, thresholdImg)
val img_bitmap =
Bitmap.createBitmap(foreground!!.cols(), foreground!!.rows(), Bitmap.Config.ARGB_8888)
Utils.matToBitmap(foreground!!, img_bitmap)
imageView.setImageBitmap(img_bitmap)
return foreground
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这项任务一点也不简单。OpenCV 有一种名为“GrabCut”的分割算法,试图解决这个特定问题。该算法非常擅长对背景和前景像素进行分类,但它需要非常具体的信息才能工作。它可以在两种模式下运行:
第一种模式(蒙版模式):使用二进制蒙版(与原始输入大小相同),其中标记 100% 确定的背景像素以及 100% 确定的前景像素。您不必标记图像上的每个像素,只需标记您确信算法会找到任一类像素的区域即可。
第二种模式(前景 ROI):使用包围 100% 明确前景像素的边界框。
现在,我使用“100% 确定”符号来标记那些您 100% 确定它们对应于前景背景的像素。该算法将像素分为四种可能的类别:“确定背景”、“可能背景”、“确定前景”和“可能前景”。它将预测可能的背景和可能的前景像素,但它需要在哪里至少找到“最终前景”像素的先验信息。
话虽如此,我们可以使用GrabCut其第二种模式(矩形 ROI)来尝试对输入图像进行分割。我们可以尝试获取输入的第一个粗略二进制掩码。这将标记我们确定算法可以找到前景像素的位置。我们将把这个粗略的掩码输入算法并检查结果。现在,该方法并不容易,其自动化也并不简单,我们将设置一些手动信息,这些信息对于该输入图像特别有效。我不知道OpenCV的Java实现,所以我给你Python的解决方案。希望您能够移植它。这是该算法的总体轮廓:
这是代码:
# imports:
import cv2
import numpy as np
# image path
path = "D://opencvImages//"
fileName = "backgroundTest.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# (Optional) Deep copy for results:
inputImageCopy = inputImage.copy()
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Adaptive Thresholding
windowSize = 31
windowConstant = 11
binaryImage = cv2.adaptiveThreshold(grayscaleImage, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, windowSize, windowConstant)
Run Code Online (Sandbox Code Playgroud)
第一步是使用自适应阈值获得粗略的前景蒙版。在这里,我使用了该ADAPTIVE_THRESH_MEAN_C方法,其中(局部)阈值是输入图像上邻域区域的平均值。这会产生以下图像:
这很粗糙,对吧?我们可以使用一些形态学来稍微清理一下。我使用Closing具有rectangular kernel大小3 x 3和10迭代的 a 来加入白色像素的大斑点。我将 OpenCV 函数包装在自定义函数中,这样可以节省我输入一些行的时间。这些辅助函数将在本文末尾介绍。目前,该步骤如下:
# Apply a morphological closing with:
# Rectangular SE size 3 x 3 and 10 iterations
binaryImage = morphoOperation(binaryImage, 3, 10, "Closing")
Run Code Online (Sandbox Code Playgroud)
这是过滤后的粗略掩模:
好一点了。好的,我们现在可以搜索最大轮廓的边界框。对于这个例子来说,通过搜索外部轮廓cv2.RETR_EXTERNAL就足够了,因为我们可以安全地忽略子轮廓,如下所示:
# Find the EXTERNAL contours on the binary image:
contours, hierarchy = cv2.findContours(binaryImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# This list will store the target bounding box
maskRect = []
Run Code Online (Sandbox Code Playgroud)
此外,让我们准备好list存储目标边界矩形的位置。现在让我们搜索检测到的轮廓。我还实现了区域过滤器,以防存在一些噪声,因此低于特定区域阈值的像素将被忽略:
# Look for the outer bounding boxes (no children):
for i, c in enumerate(contours):
# Get blob area:
currentArea = cv2.contourArea(c)
# Get the bounding rectangle:
boundRect = cv2.boundingRect(c)
# Set a minimum area
minArea = 1000
# Look for the target contour:
if currentArea > minArea:
# Found the target bounding rectangle:
maskRect = boundRect
# (Optional) Draw the rectangle on the input image:
# Get the dimensions of the bounding rect:
rectX = boundRect[0]
rectY = boundRect[1]
rectWidth = boundRect[2]
rectHeight = boundRect[3]
# (Optional) Set color and draw:
color = (0, 0, 255)
cv2.rectangle( inputImageCopy, (int(rectX), int(rectY)),
(int(rectX + rectWidth), int(rectY + rectHeight)), color, 2 )
# (Optional) Show image:
cv2.imshow("Bounding Rectangle", inputImageCopy)
cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)
您也可以选择绘制算法找到的边界框。这是生成的图像:
看起来不错。请注意,一些明显的背景像素也包含在ROI. GrabCut将尝试将这些像素重新分类为适当的类别,即“最终背景”。好吧,让我们准备数据GrabCut:
# Create mask for Grab n Cut,
# The mask is a uint8 type, same dimensions as
# original input:
mask = np.zeros(inputImage.shape[:2], np.uint8)
# Grab n Cut needs two empty matrices of
# Float type (64 bits) and size 1 (rows) x 65 (columns):
bgModel = np.zeros((1, 65), np.float64)
fgModel = np.zeros((1, 65), np.float64)
Run Code Online (Sandbox Code Playgroud)
我们需要准备三个矩阵/numpy 数组/任何用于在 Java 中表示图像的数据类型。第一个是将存储mask获得的分段的位置。GrabCut该掩码的值从0到3来表示原始输入上每个像素的类别。和矩阵被算法内部使用来存储前景和背景的统计模型bgModel。fgModel请注意,这两个矩阵都是float矩阵。最后GrabCut是迭代算法。它将运行n迭代。好的,让我们运行GrabCut:
# Run Grab n Cut on INIT_WITH_RECT mode:
grabCutIterations = 5
mask, bgModel, fgModel = cv2.grabCut(inputImage, mask, maskRect, bgModel, fgModel, grabCutIterations, mode=cv2.GC_INIT_WITH_RECT)
Run Code Online (Sandbox Code Playgroud)
好了,分类完成了。您可以尝试转换mask为(图像)可见类型以检查每个像素的标签。这是可选的,但如果您愿意这样做,您将获得4矩阵。每个班级各一份。例如,对于“最终背景”类,GrabCut发现这些是属于该类的像素(白色):
属于“可能背景”类的像素如下:
这很好,是吧?以下是属于“可能前景”类的像素:
很不错。让我们创建最终的分割掩模,因为它mask不是图像,它只是array每个像素的包含标签。我们将使用确定背景和可能背景像素来设置最终掩模,然后我们可以“标准化”数据范围并将其转换为uint8获得实际图像
# Set all definite background (0) and probable background pixels (2)
# to 0 while definite foreground and probable foreground pixels are
# set to 1
outputMask = np.where((mask == cv2.GC_BGD) | (mask == cv2.GC_PR_BGD), 0, 1)
# Scale the mask from the range [0, 1] to [0, 255]
outputMask = (outputMask * 255).astype("uint8")
Run Code Online (Sandbox Code Playgroud)
这是实际的分割掩码:
好吧,我们可以稍微清理一下这张图像,因为将前景像素错误分类为背景像素会产生一些小孔。让我们应用另一种形态学closing,这次使用5迭代:
# (Optional) Apply a morphological closing with:
# Rectangular SE size 3 x 3 and 5 iterations:
outputMask = morphoOperation(outputMask, 3, 5, "Closing")
Run Code Online (Sandbox Code Playgroud)
最后,将其与原始图像outputMask一起使用以生成最终的分割结果:AND
# Apply a bitwise AND to the image using our mask generated by
# GrabCut to generate the final output image:
segmentedImage = cv2.bitwise_and(inputImage, inputImage, mask=outputMask)
cv2.imshow("Segmented Image", segmentedImage)
cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)
这是最终结果:
如果您需要此图像的透明度,则可以非常简单地使用outputMaskas alpha channel。这是我之前使用的辅助函数:
# Applies a morpho operation:
def morphoOperation(binaryImage, kernelSize, opIterations, opString):
# Get the structuring element:
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform Operation:
if opString == "Closing":
op = cv2.MORPH_CLOSE
else:
print("Morpho Operation not defined!")
return None
outImage = cv2.morphologyEx(binaryImage, op, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
return outImage
Run Code Online (Sandbox Code Playgroud)