探索MediaPipe的人像分割

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)
        }
    }

五、分割效果

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/36874.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微擎后台getshell,低权限也可以

/web/index.php?csite&aeditor 这个文件可以编辑html,然后前台会解析成php 没测试最新版 比如编辑专题:/web/index.php?csite&aeditor&dopage&multiid0 上架抓包 改html内容为php 复制前台url 访问之 博客原文: 微擎后…

GEE入门学习,遥感云大数据分析、管理与可视化以及在林业应用丨灾害、水体与湿地领域应用丨GPT模型应用

目录 ①海量遥感数据处理与GEE云计算技术实践应用 ②GPT模型支持下的Python-GEE遥感云大数据分析、管理与可视化技术及多领域案例实践应用 ③GEE遥感云大数据林业应用典型案例实践及GPT模型应用 ④遥感云大数据在灾害、水体与湿地领域典型案例实践及GPT模型应用 ①海量遥感…

关于SpringBoot、Nginx 请求参数包含 [] 特殊符号 返回400状态

问题来源: 使用RESTful风格发送带有特殊符号(如:点、大括号等)的请求,当使用Nginx做地址映射时会返回报"HTTP Status 400-Bad Request"的错误,这个时候我们需要对Nginx的映射方式做一下调整。 Nginx调整完发现跳转后又报…

回归预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积门控循环单元多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 回归预测 | MATLAB实现WOA-CNN-GR…

ELK日志记录——Kibana组件——grok 正则捕获插件、mutate数据修改插件、multiline 多行合并插件、date 时间处理插件

grok 正则捕获插件 grok 使用文本片段切分的方式来切分日志事件 内置正则表达式调用 %{SYNTAX:SEMANTIC} ●SYNTAX代表匹配值的类型,例如,0.11可以NUMBER类型所匹配,10.222.22.25可以使用IP匹配。 ●SEMANTIC表示存储该值的一个变量声明&…

【新版系统架构】第十七章-通信系统架构设计理论与实践

软考-系统架构设计师知识点提炼-系统架构设计师教程(第2版) 第一章-绪论第二章-计算机系统基础知识(一)第二章-计算机系统基础知识(二)第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…

《数学模型(第五版)》学习笔记(1) 第1章 建立数学模型 第2章 初等模型

参考数学建模论坛《数学模型(第三版)》学习笔记 http://www.madio.net/thread-146480-1-1.html 参考视频 数模视频(姜启源、谢金星) https://www.bilibili.com/video/BV1VJ411w7r3/?spm_id_from333.788.recommend_more_video.0&vd_source3ef6540f84…

【高级程序设计语言C++】初识模板

1. 函数模板1.1函数模板的实例化1.2显示实例化1.3模板参数的匹配原则 2.类模板2.1类模板的定义格式2.2类模板的实例化 1. 函数模板 概念: 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函…

哪些软件分析工具需要使用到pdb符号文件?

目录 1、什么是pdb文件?pdb文件有哪些用途? 2、pdb文件的时间戳与pdb文件名称 3、常用软件分析工具有哪些? 4、使用Windbg调试器查看函数调用堆栈时需要加载pdb文件 4.1、给Windbg设置pdb文件路径 4.2、为什么要设置系统库pdb文件下载服…

springboot家具商城系统

开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven…

vuepress - - - 首页底部版权信息加a标签超链接跳转或备案信息跳转链接

修改前 默认的底部版权信息只能填写纯文本,加不了超链接跳转等。 对应\docs\README.md内容: 修改后 修改后,点击Zichen跳转会打开新的网页。 看官网例子 底部添加了备案号跳转链接。 找到官网的github部署的文件。点导航栏中的“指…

【从零开始学习CSS | 第一篇】选择器介绍

目录 前言: 选择器介绍: 各类选择器: 总结: 前言: 本文以及后续几篇文章我们将会集中介绍CSS中的常见选择器,选择器的出现可以让我们实现对具体的元素标签进行定制,因此我们要掌握好各类选择…

js逆向补环境-调试工具vscode与nodejs使用之无环境联调

目录 一、啊哈一、Nodejs安装1、nodejs最新版本的安装(windows)2、旧版nodejs更新成最新版本(windows)3、nodejs安装(linux) 二、vscode安装使用(windows)1、下载安装vscode2、vscode运行插件Code Runner安…

Docker 安装与基本使用

一、简介 Docker 官方文档、Docker 中文文档,更新会落后于官方文档 什么是 Docker ? Docker 是一个应用打包、分发、部署的工具,也可以把它理解为一个轻量的虚拟机,它只虚拟软件需要的运行环境,多余的一点都不要,而普…

【C++】list模拟实现

🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。 🚁 个人主页:不 良 🔥 系列专栏:🛸C 🛹Linux 📕 学习格言:博观而约取&#xff0…

LVS负载均衡集群之LVS-DR部署

目录 一、lVS-DR集群概述 二、LVS-DR数据包流向分析 四、LVS-DR特性 五、DR模式 LVS负载均衡群集部 5.0配置虚拟 IP 地址(VIP 192.168.14.180) 5.1.配置负载调度器(192.168.14.101) 5.2部署共享存储(NFS服务器:192.168.14.10…

6.2Java EE——Spring的入门程序

下面通过一个简单的入门程序演示Spring框架的使用,要求在控制台打印“张三,欢迎来到Spring”,实现步骤具体如下。 1、在IDEA中创建名称为chapter06的Maven项目,然后在pom.xml文件中加载需使用到的Spring四个基础包以及Spring依赖…

etcd的使用

什么是etcd ETCD是一个分布式、可靠的key-value存储的分布式系统,用于存储分布式系统中的关键数据;当然,它不仅仅用于存储,还提供配置共享及服务发现;基于Go语言实现 。 etcd的特点 完全复制:集群中的每…

基于springboot+Redis的前后端分离项目之分布式锁(四)-【黑马点评】

🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码:eh11 分布式锁 分布式锁1 、基本原理和实现方式对比2 、Redis分布式锁的实现核心思路3 、实现分布式锁版本一4 、Redis分布式锁误删情况…

Linux进程信号

文章目录: 信号入门从生活角度看信号技术应用角度看信号使用 kill -l 命令查看系统定义的信号列表信号处理常见方式概览 产生信号通过终端按键产生信号核心转储(core dump)的作用调用系统函数向进程发信号由软件条件产生信号硬件异常产生信号…