Java 在Android中使用OpenCV从图像中删除背景

Java 在Android中使用OpenCV从图像中删除背景,java,python,android,opencv,Java,Python,Android,Opencv,我想删除Android中打开CV的图像背景。代码运行良好,但输出质量不符合预期。我遵循java文档进行代码参考: 谢谢 原始图像 我的输出 预期产量 我在Android中的代码片段: private fun doBackgroundRemoval(frame: Mat): Mat? { // init val hsvImg = Mat() val hsvPlanes: List<Mat> = ArrayList() val thresholdIm

我想删除Android中打开CV的图像背景。代码运行良好,但输出质量不符合预期。我遵循java文档进行代码参考:

谢谢

原始图像

我的输出 预期产量

我在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
}
private fun Dobackground移除(框架:垫):垫?{
//初始化
val hsvImg=Mat()
val hsvPlanes:List=ArrayList()
val thresholdImg=Mat()
var thresh\u type=Imgproc.thresh\u BINARY\u INV
thresh_type=Imgproc.thresh_二进制文件
//使用平均色调值设置图像阈值
创建(frame.size(),CvType.CV_8U)
Imgproc.cvt颜色(帧、hsvImg、Imgproc.COLOR\u BGR2HSV)
堆芯分离(高速VIMG、高速VPLANES)
//获取图像的平均色调值
val threshValue:Double=getHistAverage(hsvImg,hsvPlanes[0])
阈值(hsvPlanes[0],thresholdImg,ThresholdValue,78.0,threshold_类型)
模糊(thresholdImg,thresholdImg,Size(1.toDouble(),1.toDouble())
瓦尔核1=
getStructuringElement(Imgproc.morp_椭圆,大小(11.toDouble(),11.toDouble())
val kernel2=材料(3,3,CvType.CV_8U)
//扩张以填充间隙,侵蚀以平滑边缘
放大(阈值img,阈值img,核1,点(-1.toDouble(),-1.toDouble()),1)
侵蚀(阈值img,阈值img,核2,点(-1.toDouble(),-1.toDouble()),7)
阈值(thresholdImg,thresholdImg,ThresholdValue,255.0,Imgproc.THRESH\u BINARY\u INV)
//创建新图像
val前景=Mat(
frame.size(),CvType.CV_8UC3,标量(
255.toDouble(),
255.toDouble(),
255.toDouble()
)
)
frame.copyTo(前景,阈值img)
val img_位图=
创建位图(前台!!.cols(),前台!!.rows(),位图.Config.ARGB_8888)
Utils.matToBitmap(前景!!,img\u位图)
设置图像位图(img_位图)
返回前景
}

正如您所看到的,这项任务一点也不琐碎。OpenCV有一个名为的分割算法,试图解决这个特殊问题。该算法非常擅长于对背景和前景像素进行分类,但是它需要非常具体的信息才能工作。它可以在两种模式下运行:

  • 第一模式(遮罩模式):使用二进制遮罩(与原始输入大小相同),其中100%确定的背景像素被标记,如下所示: 以及100%确定的前景像素。你不必每次都做标记 图像上的像素,只是一个区域,您可以确定算法 将找到任意一类像素

  • 第二模式(前景ROI):使用包围100%确定前景像素的边界框

现在,我使用符号“100%确定”来标记那些像素,您100%确定它们对应于前景的背景。该算法将像素分为四类:“确定背景”“可能背景”“确定前景”“可能前景”。它将预测可能的背景和可能的前景像素,但它需要一个先验信息,至少在哪里可以找到“确定的前景”像素

也就是说,我们可以在第二种模式(矩形ROI)中使用
GrabCut
,尝试分割输入图像。我们可以尝试获得输入的第一个粗略的二进制掩码。这将标记我们确信算法可以找到前景像素的位置。我们将把这个粗略的掩码提供给算法,并检查结果。现在,这个方法不容易,自动化也不简单,我们将设置一些手动信息,这些信息对于这个输入图像特别有效。我不知道OpenCV的Java实现,所以我将为您提供Python的解决方案。希望你能移植它。这是该算法的概述:

  • 通过阈值化获得前景对象的第一个粗糙遮罩
  • 在粗糙遮罩上检测轮廓,以检索边界矩形
  • 边框将用作GrabCut算法的输入ROI
  • 设置GrabCut算法所需的参数
  • 清洁通过GrabCut获得的分割遮罩
  • 使用分割遮罩最后分割前景对象
  • 代码如下:

    # 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)
    
    第一步是使用自适应阈值获得粗略的前景遮罩。这里,我使用了
    ADAPTIVE_THRESH_MEAN_C
    方法,其中(局部)阈值是输入图像上邻域区域的平均值。这将产生以下图像:

    很粗糙,对吧?我们可以用一些形态学来清理一下。我使用一个
    Closing
    3x3
    大小的
    矩形内核
    10
    迭代来加入白色像素的大斑点。我已经将OpenCV函数包装在自定义函数中,这样可以节省键入某些行的时间。这些助手函数将在本文末尾介绍。目前,该步骤如下所示:

    # Apply a morphological closing with:
    # Rectangular SE size 3 x 3 and 10 iterations
    binaryImage = morphoOperation(binaryImage, 3, 10, "Closing")
    
    这是过滤后的粗糙遮罩:

    好一点。好的,我们现在可以搜索最大轮廓的边界框。通过
    cv2.RETR\u 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 = []
    
    另外,让我们准备一个
    列表
    来存储目标边界矩形。现在让我们搜索检测到的轮廓。我还实现了一个区域过滤器,以防出现噪声,因此忽略低于某个区域阈值的像素:

    # 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)
    
    (可选)可以绘制由算法找到的边界框。这是第
    # 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 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)
    
    # 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")
    
    # (Optional) Apply a morphological closing with:
    # Rectangular SE size 3 x 3 and 5 iterations:
    outputMask = morphoOperation(outputMask, 3, 5, "Closing")
    
    # 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)
    
    # 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
    
    import cv2
    import numpy as np
    
    def process(img):
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        img_canny = cv2.Canny(img_gray, 10, 20)
        kernel = np.ones((13, 13))
        img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
        return cv2.erode(img_dilate, kernel, iterations=1)
        
    def get_mask(img):
        contours, _ = cv2.findContours(process(img), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        blank = np.zeros(img.shape[:2]).astype('uint8')
        for cnt in contours:
            if cv2.contourArea(cnt) > 500:
                peri = cv2.arcLength(cnt, True)
                approx = cv2.approxPolyDP(cnt, peri * 0.004, True)
                cv2.drawContours(blank, [approx], -1, 255, -1) 
        return blank
    
    img = cv2.imread("crystal.jpg")
    img_masked = cv2.bitwise_and(img, img, mask=get_mask(img))
    
    cv2.imshow("Masked", img_masked)
    cv2.waitKey(0)
    
    import cv2
    import numpy as np
    
    def process(img):
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        img_canny = cv2.Canny(img_gray, 10, 20)
        kernel = np.ones((13, 13))
        img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
        return cv2.erode(img_dilate, kernel, iterations=1)
    
    def get_mask(img):
        contours, _ = cv2.findContours(process(img), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        blank = np.zeros(img.shape[:2]).astype('uint8')
        for cnt in contours:
            if cv2.contourArea(cnt) > 500:
                peri = cv2.arcLength(cnt, True)
                approx = cv2.approxPolyDP(cnt, peri * 0.004, True)
                cv2.drawContours(blank, [approx], -1, 255, -1) 
        return blank
    
    img = cv2.imread("crystal.jpg")
    img_masked = cv2.bitwise_and(img, img, mask=get_mask(img))
    
    cv2.imshow("Masked", img_masked)
    cv2.waitKey(0)
    
    img = cv2.imread("crystal.jpg")
    img_masked = cv2.merge(cv2.split(img) + [get_mask(img)])
    cv2.imwrite("masked_crystal.png", img_masked)
    
    img = cv2.imread("crystal.jpg")
    
    img_masked = cv2.merge(cv2.split(img) + [get_mask(img)])
    
    cv2.imwrite("masked_crystal.png", img_masked)