探索MediaPipe的人像分割

news/2024/11/8 20:49:11/

MediaPipe是Google开源的计算机视觉处理框架,基于TensorFlow来训练模型。图像分割模块提供人像分割、头发分割、多类分割。本文主要探索如何实现人像分割,当然在人像分割基础上,我们可以做背景替换、背景模糊。

目录

一、配置参数与模型

1、配置参数

2、分割模型

2.1 人像分割模型

2.2  头发分割模型

2.3 多类分割模型

二、工程配置

三、初始化工作

1、初始化人像分割

2、初始化摄像头

四、人像分割

1、运行人像分割 

2、绘制人像分割

五、分割效果

一、配置参数与模型

1、配置参数

图像分割的参数包括:运行模式、输出类别掩码、输出置信度掩码、标签语言、结果回调,具体如下表所示:

参数描述取值范围默认值
running_mode

IMAGE:单个图像

VIDEO:视频帧

LIVE_STREAM:实时流

{IMAGE,VIDEO,

LIVE_STREAM}

IMAGE
output_category_mask输出的类别掩码Booleanfalse
output_confidence_mask输出的置信度掩码Booleantrue
display_names_locale标签名称的语言Locale codeen
result_callback结果回调(用于LIVE_STREAM模式)N/AN/A

2、分割模型

图像分割的模型有deeplabv3、haird_segmenter、selfie_multiclass、selfie_segmenter。其中,自拍的人像分割使用的模型是selfie_segmenter。相关模型如下图所示:

2.1 人像分割模型

人像分割输出两类结果:0表示背景、1表示人像。提供两种形状的模型,如下图所示:

 

2.2  头发分割模型

头发分割也是输出两类结果:0表示背景、1表示头发。我们在识别到头发时,可以重新给头发上色,或者添加特效。

2.3 多类分割模型

多类分割包括:背景、头发、身体皮肤、面部皮肤、衣服、其他部位。数值对应关系如下:

0 - background
1 - hair
2 - body-skin
3 - face-skin
4 - clothes
5 - others (accessories)

二、工程配置

以Android平台为例,首先导入MediaPipe相关包:

implementation 'com.google.mediapipe:tasks-vision:0.10.0'

然后运行下载模型的task,并且指定模型保存路径:

project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets'apply from: 'download_models.gradle'

图像分割模型有4种,可以按需下载:

task downloadSelfieSegmenterModelFile(type: Download) {src 'https://storage.googleapis.com/mediapipe-models/image_segmenter/' +'selfie_segmenter/float16/1/selfie_segmenter.tflite'dest project.ext.ASSET_DIR + '/selfie_segmenter.tflite'overwrite false
}preBuild.dependsOn downloadSelfieSegmenterModelFile

三、初始化工作

1、初始化人像分割

人像分割的初始化主要有:设置运行模式、加载对应的分割模型、配置参数,示例代码如下:

   fun setupImageSegmenter() {val baseOptionsBuilder = BaseOptions.builder()// 设置运行模式when (currentDelegate) {DELEGATE_CPU -> {baseOptionsBuilder.setDelegate(Delegate.CPU)}DELEGATE_GPU -> {baseOptionsBuilder.setDelegate(Delegate.GPU)}}// 加载对应的分割模型when(currentModel) {MODEL_DEEPLABV3 -> { //DeepLab V3baseOptionsBuilder.setModelAssetPath(MODEL_DEEPLABV3_PATH)}MODEL_HAIR_SEGMENTER -> { // 头发分割baseOptionsBuilder.setModelAssetPath(MODEL_HAIR_SEGMENTER_PATH)}MODEL_SELFIE_SEGMENTER -> { // 人像分割baseOptionsBuilder.setModelAssetPath(MODEL_SELFIE_SEGMENTER_PATH)}MODEL_SELFIE_MULTICLASS -> { // 多类分割baseOptionsBuilder.setModelAssetPath(MODEL_SELFIE_MULTICLASS_PATH)}}try {val baseOptions = baseOptionsBuilder.build()val optionsBuilder = ImageSegmenter.ImageSegmenterOptions.builder().setRunningMode(runningMode).setBaseOptions(baseOptions).setOutputCategoryMask(true).setOutputConfidenceMasks(false)// 检测结果异步回调if (runningMode == RunningMode.LIVE_STREAM) {optionsBuilder.setResultListener(this::returnSegmentationResult).setErrorListener(this::returnSegmentationHelperError)}val options = optionsBuilder.build()imagesegmenter = ImageSegmenter.createFromOptions(context, options)} catch (e: IllegalStateException) {imageSegmenterListener?.onError("Image segmenter failed to init, error:${e.message}")} catch (e: RuntimeException) {imageSegmenterListener?.onError("Image segmenter failed to init. error:${e.message}", GPU_ERROR)}}

2、初始化摄像头

摄像头的初始化步骤包括:获取CameraProvider、设置预览宽高比、配置图像分析参数、绑定camera生命周期、关联SurfaceProvider。

    private fun setUpCamera() {val cameraProviderFuture =ProcessCameraProvider.getInstance(requireContext())cameraProviderFuture.addListener({// 获取CameraProvidercameraProvider = cameraProviderFuture.get()// 绑定camerabindCamera()}, ContextCompat.getMainExecutor(requireContext()))}private fun bindCamera() {val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera init failed.")val cameraSelector = CameraSelector.Builder().requireLensFacing(cameraFacing).build()// 预览宽高比设置为4:3preview = Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_3).setTargetRotation(fragmentCameraBinding.viewFinder.display.rotation).build()// 配置图像分析的参数imageAnalyzer =ImageAnalysis.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_3).setTargetRotation(fragmentCameraBinding.viewFinder.display.rotation).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888).build().also {it.setAnalyzer(backgroundExecutor!!) { image ->imageSegmenterHelper.segmentLiveStreamFrame(image,cameraFacing == CameraSelector.LENS_FACING_FRONT)}}cameraProvider.unbindAll()try {// 绑定camera生命周期camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalyzer)// 关联SurfaceProviderpreview?.setSurfaceProvider(fragmentCameraBinding.viewFinder.surfaceProvider)} catch (exc: Exception) {Log.e(TAG, "Use case binding failed", exc)}}

四、人像分割

1、运行人像分割 

以摄像头的LIVE_STREAM模式为例,首先拷贝图像数据,然后图像处理:旋转与镜像,接着把Bitmap对象转换为MPImage,最后执行人像分割。示例代码如下:

    fun segmentLiveStreamFrame(imageProxy: ImageProxy, isFrontCamera: Boolean) {val frameTime    = SystemClock.uptimeMillis()val bitmapBuffer = Bitmap.createBitmap(imageProxy.width,imageProxy.height, Bitmap.Config.ARGB_8888)// 拷贝图像数据imageProxy.use {bitmapBuffer.copyPixelsFromBuffer(imageProxy.planes[0].buffer)}val matrix = Matrix().apply {// 旋转图像postRotate(imageProxy.imageInfo.rotationDegrees.toFloat())// 如果是前置camera,需要左右镜像if(isFrontCamera) {postScale(-1f, 1f, imageProxy.width.toFloat(), imageProxy.height.toFloat())}}imageProxy.close()val rotatedBitmap = Bitmap.createBitmap(bitmapBuffer, 0, 0,bitmapBuffer.width, bitmapBuffer.height, matrix, true)// 转换Bitmap为MPImageval mpImage = BitmapImageBuilder(rotatedBitmap).build()// 执行人像分割imagesegmenter?.segmentAsync(mpImage, frameTime)}

2、绘制人像分割

首先把检测到的背景标记颜色,然后计算缩放系数,主动触发draw操作:

    fun setResults(byteBuffer: ByteBuffer,outputWidth: Int,outputHeight: Int) {val pixels = IntArray(byteBuffer.capacity())for (i in pixels.indices) {// Deeplab使用0表示背景,其他标签为1-19. 所以这里使用20种颜色val index = byteBuffer.get(i).toUInt() % 20Uval color = ImageSegmenterHelper.labelColors[index.toInt()].toAlphaColor()pixels[i] = color}val image = Bitmap.createBitmap(pixels, outputWidth, outputHeight, Bitmap.Config.ARGB_8888)// 计算缩放系数val scaleFactor = when (runningMode) {RunningMode.IMAGE,RunningMode.VIDEO -> {min(width * 1f / outputWidth, height * 1f / outputHeight)}RunningMode.LIVE_STREAM -> {max(width * 1f / outputWidth, height * 1f / outputHeight)}}val scaleWidth = (outputWidth * scaleFactor).toInt()val scaleHeight = (outputHeight * scaleFactor).toInt()scaleBitmap = Bitmap.createScaledBitmap(image, scaleWidth, scaleHeight, false)invalidate()}

最终执行绘制的draw函数,调用canvas来绘制bitmap:

    override fun draw(canvas: Canvas) {super.draw(canvas)scaleBitmap?.let {canvas.drawBitmap(it, 0f, 0f, null)}}

五、分割效果

人像分割本质是把人像和背景分离,最终效果图如下: 

 


http://www.ppmy.cn/news/588329.html

相关文章

已解决【Watch检查软件更新失败,因为你尚未接入互联网。】

未配对的Watch无法成功通过以下问题的解决方案: 无法检查更新 检查软件更新失败,因为你尚未接入互联网。 没有授予Watch这个App的网络权限,可在设置-蜂窝网络里进行修改。将手机连接的 WiFi 频段修改为2.4GHz,不要使用5GHz。关闭手…

iPhone系统关闭自动更新并去除设置上的红点

iPhone设置软件上一直有消息1,是不是很烦。今天给大家分享一个关闭自动更新的方法。 准备工具如下: (1)PC机 (2)iPhone (3)数据线 关闭自动更新的步骤 **(1&#xff0…

美版iphone更新系统无服务器,无法连接iphone软件更新服务器【应对攻略】

喜欢使用电脑的小伙伴们一般都会遇到win7系统无法连接iphone软件更新服务器的问题,突然遇到win7系统无法连接iphone软件更新服务器的问题就不知道该怎么办了,其实win7系统无法连接iphone软件更新服务器的解决方法非常简单,按照1:同…

ios更新显示“已请求更新“但是没反应,最佳实践

发文章不能说脏话,但是我真T的想说卧~ 那些发水文的你脑壳有吗? 无论是技术文章还是平常生活中的一个指引,首先要保证真实性、可靠性。 胡编乱造,误导人的水文,被抄来抄去…… 文章内容千篇一律,且不具…

iphone 扩容测试软件,六个扩大iPhone储存空间的方法,亲测有效!

原标题:六个扩大iPhone储存空间的方法,亲测有效! 对于热爱摄影的iPhone用户来说,最担心的不是iPhone的拍照功能,而是它的储存空间。好不容易拍了一组十分满意的照片,却因为剩余内存太少而无法全部储存&…

iPhone升级iOS 15卡在请求更新上怎么办?

iOS 15正式版已经发布,很多小伙伴反馈iPhone在升级iOS 15过程中卡在请求更新上,无法继续更新。造成这个问题的原因有很多,包括WiFi网络问题或者手机软件故障等。在本文中,我们将介绍为什么iPhone会卡在请求更新上,以及…

让iPhone不能自动下载系统更新的一个办法

未越狱的iPhone总是会自动下载更新,弹出更新提示,十分烦人,通过抓包发现iPhone会从appldnld.apple.com这个服务器下载更新固件, 于是就想从dns解析下手,改变appldnld.apple.com这个域名指向的IP,刚好我自己…

苹果怎么关闭自动更新系统_iPhone系统关闭自动更新教程

孤岛小休ID:xiaoxiuvip 新朋友点上方蓝字关注小休的订阅号,欢迎来调戏。 iPhone用户一定总会遇到这样的情况:刚刚适应了新的iOS系统,正是用得顺手的时候,却“嘭”地一下又弹出了一个对话框,提醒你又该更新iOS系统啦!无奈的是,苹果公司却并没有为这些不愿意实时更新iOS系…