探索MediaPipe的人像分割

news2024/9/27 23:22:46

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 V3
                baseOptionsBuilder.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(
            {
                // 获取CameraProvider
                cameraProvider = cameraProviderFuture.get()
                // 绑定camera
                bindCamera()
            }, ContextCompat.getMainExecutor(requireContext())
        )
    }

    private fun bindCamera() {
        val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera init failed.")
        val cameraSelector = CameraSelector.Builder().requireLensFacing(cameraFacing).build()

        // 预览宽高比设置为4:3
        preview = 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)
            // 关联SurfaceProvider
            preview?.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为MPImage
        val 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() % 20U
            val 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.coloradmin.cn/o/688442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

负载均衡 Load Balancing

负载均衡 Load Balancing 数据链路层负载均衡网络层负载均衡应用层负载均衡均衡策略与实现轮询与随机随机权重与加权轮询一致性 hash最少活跃数(最少连接数) 对于电商平台而言,随着业务的不断发展壮大,网站访问量和数据量也随之急…

什么是堆栈?c语言实现栈上的pop和push操作

什么是堆栈?c语言实现栈上pop和push操作的算法 堆栈(stack)实现栈上pop和push操作解释 堆栈(stack) 堆栈(stack)是一种数据结构,它具有后进先出(Last-In-First-Out&…

抖音seo矩阵系统源码开发部署思路

抖音SEO矩阵系统源码开发和部署是一项复杂而又关键的任务。在开发阶段,我们需要根据抖音的搜索规则和算法,结合用户搜索意图和关键词,进行深入的研究和分析,以建立一个优秀的SEO矩阵系统。 在部署方面,我们需要遵循以…

完全零基础,如何学习渗透?

网络渗透这一行,做个脚本小子,使使工具啥的,个把月就学会了,这不难。 很多人把网络渗透简单的理解成就是搞网站,这是大错特错的! 不过这也难怪,Web安全那三招两式,最好教也最好学&…

还在找wma格式怎么转换mp3?

在一个遥远的国度里,有一位名叫小芳的歌手,她声音甜美动人,深受人们的喜爱。然而,她的音乐制作团队告诉她,她的歌曲只能以wma格式发布。但是,她的粉丝们大多数使用的是mp3格式的音乐播放器,这让…

【Java】JVM(五)

垃圾回收机制 判断对象的存活 在堆里面存放着几乎所有的对象实例,垃圾回收器在对对进行回收前,要做的事情就是确定这些对象中哪些还是“存活”着,哪些已经“死去”(死去代表着不可能再被任何途径使用得对象了) 可达…

【pytest学习总结2.3】 - 如何使用固定装置fixtures(2)

目录 2.3.8 使用mark给固定装置传递数据 2.3.9 将固定装置工厂化 2.3.10 参数化固定装置 2.3.11 使用带有参数化固定装置的标记 2.3.12 使用来自固定装置功能中的固定装置 - 模块化 2.3.13 按固定装置实例自动分组测试 2.3.14 在类和模块中使用usefixtures 2.3.15 固定…

四.安防摄像机的WDR(HDR) 性能

四.安防摄像机的WDR(HDR) 性能 4.0 概述 WDR就是宽动态,wide dynamic range,或者HDR,high dynamic range,本质上是一回事,没任何区别。那么,到底什么是宽呢?很简单,搞不定的,就是太宽了,比如 摄像机装在室内,室内正常照度500lux,玻璃门外就是10000lux,室内外…

单机取证-鉴于信息安全管理与评估赛项-计算机单机取证特别说明-例题详解-Autopsy使用

芜湖~ 本期针对全国职业技能大赛-信息安全管理与评估赛项分析一下单机取证这个大项 并且取一例题 进行例题讲解 分享一些思路和逻辑 目录 题目 前言分析 .E01 ⽂件 DD 镜像和 E01 镜像的主要区别 如何打开和查看 E01 ⽂件扩展名? 常用工具使用-Autopsy 正…

现在这个年代,还有必要进行JVM调优吗?

导言 随着技术的不断发展,软件开发行业也在日新月异地进步。在过去的几十年里,Java语言和Java虚拟机(JVM)在开发企业级应用方面扮演了重要角色。然而,随着硬件和软件的进步,以及JVM本身的改进,…

Linux驱动学习(4) MTD字符驱动和块驱动1

系列文章目录 Linux驱动学习(4) 文章目录 目录 目录 系列文章目录 文章目录 前言 一、MTD是什么? 二、MTD子系统架构 1.Linux文件存储基本架构: ​ 2.MTD子系统基本架构: 总结 前言 MTD设备在嵌入式设备中…

2023网安面试题164道(附答案)

最近有不少小伙伴跑来咨询: 想找网络安全工作,应该要怎么进行技术面试准备?工作不到 2 年,想跳槽看下机会,有没有相关的面试题呢? 为了更好地帮助大家高薪就业,今天就给大家分享两份网络安全工…

【pytest学习总结2.2 】- 如何在测试中编写断言?

目录 2.2 如何在测试中编写断言 2.2.1 assert 断言 2.2.2 断言预期的异常 2.2.3 断言预期警告 【后续内容更新】 2.2.4 断言上下文 2.2.5 为失败的断言定义您自己的解释 2.2.6 断言的详细内容 🎁更多干货 完整版文档下载方式: 2.2 如何在测试中…

C++ 哈希思想 unordered_set unordered_map

文章目录 哈希思想常用的哈希函数哈希冲突解决方案哈希代码实现(C 源码)unordered_set & unordered_map 容器**unordered_set & unordered_map模拟实现**(C 源码) 哈希思想 抽象感受哈希的优点 如果我现在抛出一个问题&a…

Python+OpenCV 实现图像位平面分层进行图像信息隐藏

引言 闲言:这篇博客回归了传统图像处理领域,主要是在研究生的数字图像处理课程上接触到了新的知识–图像位平面,觉得还挺有意思的,可以用来做信息隐藏,索性记录一下。因为拖延的缘故,到学期末才赶出来一篇&…

计算机毕业论文内容参考|基于Android的旅游攻略APP的设计与实现

文章目录 导文摘要:前言:绪论:1. 课题背景:2. 国内外现状与趋势:3. 课题内容:相关技术与方法介绍:系统分析:系统设计:系统实现系统测试总结与展望本文总结后续工作展望导文 计算机毕业论文内容参考|基于Android的旅游攻略APP的设计与实现 摘要: 本文基于Android平台…

python单元测试框架Unittest详解

前言 我们今天来聊聊Python中的单元测试框架unittest,大家都知道在Python中比较热门的测试框架除了pytest就是unittest,我之前有讲过pytest所以今天想讲unittest。喜欢的可以点点关注哟。 Unittest详解 Unittest是Python内部自带的一个单元测试的模块&…

mysql——初步认识

数据库是什么? 数据库(Database)是按照数据结构来组织、存储和管理数据的仓库 说人话 就是 数据库是一个电子仓库,里面存了一些数据 我们要学习的mysql就是数据库中的一种,并且是一种关系型数据库,当然有…

ASEMI快恢复二极管MUR20100DCR的性能与应用

编辑-Z 本文主要介绍了MUR20100DCR二极管的性能与应用。我们将对MUR20100DCR二极管的基本性能、不同领域的应用和优势与不足进行分析。 1、MUR20100DCR二极管的基本性能 MUR20100DCR二极管是一种高性能的超快速二极管,具有高电压、高电流和低漏电流等特点。它采用…

基于Python所写的企业编码生成系统

点击以下链接获取源码资源: https://download.csdn.net/download/qq_64505944/87950401?spm1001.2014.3001.5503 在PyCharm中运行《企业编码生成系统》即可进入如图1所示的系统主界面。在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&…