Java 在Android中使用OpenCV从图像中删除背景
我想删除Android中打开CV的图像背景。代码运行良好,但输出质量不符合预期。我遵循java文档进行代码参考: 谢谢 原始图像 我的输出 预期产量 我在Android中的代码片段: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
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%确定前景像素的边界框
GrabCut
,尝试分割输入图像。我们可以尝试获得输入的第一个粗略的二进制掩码。这将标记我们确信算法可以找到前景像素的位置。我们将把这个粗略的掩码提供给算法,并检查结果。现在,这个方法不容易,自动化也不简单,我们将设置一些手动信息,这些信息对于这个输入图像特别有效。我不知道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)
第一步是使用自适应阈值获得粗略的前景遮罩。这里,我使用了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)