Android 解决CameraView叠加2个以上滤镜拍照黑屏的BUG (二)

news2024/11/24 4:48:59

1. 前言

这段时间,在使用 natario1/CameraView 来实现带滤镜的预览拍照录像功能。
由于CameraView封装的比较到位,在项目前期,的确为我们节省了不少时间。
但随着项目持续深入,对于CameraView的使用进入深水区,逐渐出现满足不了我们需求的情况。
特别是对于使用MultiFilter,叠加2个滤镜拍照是正常的,叠加2个以上滤镜拍照,预览时正常,拍出的照片就会全黑。
Github中的issues中,也有不少提这个BUG的,但是作者一直没有修复该问题。

在这里插入图片描述

上篇文章,我们已经对带滤镜拍照的整个流程有了大概的了解,这篇文章,我们重点来看takeFrame方法,这是带滤镜拍照的核心代码。

接下来我们就来解析takeFrame的源码

2. 创建EGL窗口

首先,会创建EGL窗口,这里创建了一个假的,前台不可见的一个EGL窗口,专门用来保存图片

// 0. EGL window will need an output.
// We create a fake one as explained in javadocs.
final int fakeOutputTextureId = 9999;
SurfaceTexture fakeOutputSurface = new SurfaceTexture(fakeOutputTextureId);
fakeOutputSurface.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());

3. 创建EGL Surface

接着,来创建EglSurface

// 1. Create an EGL surface
final EglCore core = new EglCore(eglContext, EglCore.FLAG_RECORDABLE);
final EglSurface eglSurface = new EglWindowSurface(core, fakeOutputSurface);
eglSurface.makeCurrent();

3.1 EglSurface

其中,这个com.otaliastudios.opengl.EglSurface是作者自己创建的,继承自EglNativeSurface

public open class EglNativeSurface internal constructor(
        internal var eglCore: EglCore,
        internal var eglSurface: EglSurface) {

    private var width = -1
    private var height = -1

    /**
     * Can be called by subclasses whose width is guaranteed to never change,
     * so we can cache this value. For window surfaces, this should not be called.
     */
    @Suppress("unused")
    protected fun setWidth(width: Int) {
        this.width = width
    }

    /**
     * Can be called by subclasses whose height is guaranteed to never change,
     * so we can cache this value. For window surfaces, this should not be called.
     */
    @Suppress("unused")
    protected fun setHeight(height: Int) {
        this.height = height
    }

    /**
     * Returns the surface's width, in pixels.
     *
     * If this is called on a window surface, and the underlying surface is in the process
     * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged"
     * callback).  The size should match after the next buffer swap.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    public fun getWidth(): Int {
        return if (width < 0) {
            eglCore.querySurface(eglSurface, EGL_WIDTH)
        } else {
            width
        }
    }

    /**
     * Returns the surface's height, in pixels.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    public fun getHeight(): Int {
        return if (height < 0) {
            eglCore.querySurface(eglSurface, EGL_HEIGHT)
        } else {
            height
        }
    }

    /**
     * Release the EGL surface.
     */
    public open fun release() {
        eglCore.releaseSurface(eglSurface)
        eglSurface = EGL_NO_SURFACE
        height = -1
        width = -1
    }

    /**
     * Whether this surface is current on the
     * attached [EglCore].
     */
    @Suppress("MemberVisibilityCanBePrivate")
    public fun isCurrent(): Boolean {
        return eglCore.isSurfaceCurrent(eglSurface)
    }

    /**
     * Makes our EGL context and surface current.
     */
    @Suppress("unused")
    public fun makeCurrent() {
        eglCore.makeSurfaceCurrent(eglSurface)
    }

    /**
     * Makes no surface current for the attached [eglCore].
     */
    @Suppress("unused")
    public fun makeNothingCurrent() {
        eglCore.makeCurrent()
    }

    /**
     * Sends the presentation time stamp to EGL.
     * [nsecs] is the timestamp in nanoseconds.
     */
    @Suppress("unused")
    public fun setPresentationTime(nsecs: Long) {
        eglCore.setSurfacePresentationTime(eglSurface, nsecs)
    }
}

3.2 EglCore

可以看到EglNativeSurface内部其实基本上就是调用的EglCoreEglCore内部封装了EGL相关的方法。
这里的具体实现我们不需要细看,只需要知道EglSurface是作者自己实现的一个Surface就可以了,内部封装了EGL,可以实现和GlSurfaceView类似的一些功能,在这里使用的EglSurface是专门给拍照准备的。

这样做的好处在于拍照的时候,预览界面(GLSurfaceView)不会出现卡顿的现象,但是坏处也显而易见,就是可能会出现预览效果和拍照的实际效果不一致的情况。(也就是本文所遇到的BUG的情况)

OpenGL是一个跨平台的操作GPUAPIOpenGL需要本地视窗系统进行交互,就需要一个中间控制层。
EGL就是连接OpenGL ES和本地窗口系统的接口,引入EGL就是为了屏蔽不同平台上的区别。

public expect class EglCore : EglNativeCore

public open class EglNativeCore internal constructor(sharedContext: EglContext = EGL_NO_CONTEXT, flags: Int = 0) {

    private var eglDisplay: EglDisplay = EGL_NO_DISPLAY
    private var eglContext: EglContext = EGL_NO_CONTEXT
    private var eglConfig: EglConfig? = null
    private var glVersion = -1 // 2 or 3

    init {
        eglDisplay = eglGetDefaultDisplay()
        if (eglDisplay === EGL_NO_DISPLAY) {
            throw RuntimeException("unable to get EGL14 display")
        }

        if (!eglInitialize(eglDisplay, IntArray(1), IntArray(1))) {
            throw RuntimeException("unable to initialize EGL14")
        }

        // Try to get a GLES3 context, if requested.
        val chooser = EglNativeConfigChooser()
        val recordable = flags and FLAG_RECORDABLE != 0
        val tryGles3 = flags and FLAG_TRY_GLES3 != 0
        if (tryGles3) {
            val config = chooser.getConfig(eglDisplay, 3, recordable)
            if (config != null) {
                val attributes = intArrayOf(EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE)
                val context = eglCreateContext(eglDisplay, config, sharedContext, attributes)
                try {
                    Egloo.checkEglError("eglCreateContext (3)")
                    eglConfig = config
                    eglContext = context
                    glVersion = 3
                } catch (e: Exception) {
                    // Swallow, will try GLES2
                }
            }
        }

        // If GLES3 failed, go with GLES2.
        val tryGles2 = eglContext === EGL_NO_CONTEXT
        if (tryGles2) {
            val config = chooser.getConfig(eglDisplay, 2, recordable)
            if (config != null) {
                val attributes = intArrayOf(EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE)
                val context = eglCreateContext(eglDisplay, config, sharedContext, attributes)
                Egloo.checkEglError("eglCreateContext (2)")
                eglConfig = config
                eglContext = context
                glVersion = 2
            } else {
                throw RuntimeException("Unable to find a suitable EGLConfig")
            }
        }
    }

    /**
     * Discards all resources held by this class, notably the EGL context.  This must be
     * called from the thread where the context was created.
     * On completion, no context will be current.
     */
    internal open fun release() {
        if (eglDisplay !== EGL_NO_DISPLAY) {
            // Android is unusual in that it uses a reference-counted EGLDisplay.  So for
            // every eglInitialize() we need an eglTerminate().
            eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)
            eglDestroyContext(eglDisplay, eglContext)
            eglReleaseThread()
            eglTerminate(eglDisplay)
        }
        eglDisplay = EGL_NO_DISPLAY
        eglContext = EGL_NO_CONTEXT
        eglConfig = null
    }

    /**
     * Makes this context current, with no read / write surfaces.
     */
    internal open fun makeCurrent() {
        if (!eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglContext)) {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    /**
     * Destroys the specified surface.  Note the EGLSurface won't actually be destroyed if it's
     * still current in a context.
     */
    internal fun releaseSurface(eglSurface: EglSurface) {
        eglDestroySurface(eglDisplay, eglSurface)
    }

    /**
     * Creates an EGL surface associated with a Surface.
     * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
     */
    internal fun createWindowSurface(surface: Any): EglSurface {
        // Create a window surface, and attach it to the Surface we received.
        val surfaceAttribs = intArrayOf(EGL_NONE)
        val eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig!!, surface, surfaceAttribs)
        Egloo.checkEglError("eglCreateWindowSurface")
        if (eglSurface === EGL_NO_SURFACE) throw RuntimeException("surface was null")
        return eglSurface
    }

    /**
     * Creates an EGL surface associated with an offscreen buffer.
     */
    internal fun createOffscreenSurface(width: Int, height: Int): EglSurface {
        val surfaceAttribs = intArrayOf(EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE)
        val eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig!!, surfaceAttribs)
        Egloo.checkEglError("eglCreatePbufferSurface")
        if (eglSurface === EGL_NO_SURFACE) throw RuntimeException("surface was null")
        return eglSurface
    }

    /**
     * Makes our EGL context current, using the supplied surface for both "draw" and "read".
     */
    internal fun makeSurfaceCurrent(eglSurface: EglSurface) {
        if (eglDisplay === EGL_NO_DISPLAY) logv("EglCore", "NOTE: makeSurfaceCurrent w/o display")
        if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    /**
     * Makes our EGL context current, using the supplied "draw" and "read" surfaces.
     */
    internal fun makeSurfaceCurrent(drawSurface: EglSurface, readSurface: EglSurface) {
        if (eglDisplay === EGL_NO_DISPLAY) logv("EglCore", "NOTE: makeSurfaceCurrent w/o display")
        if (!eglMakeCurrent(eglDisplay, drawSurface, readSurface, eglContext)) {
            throw RuntimeException("eglMakeCurrent(draw,read) failed")
        }
    }

    /**
     * Calls eglSwapBuffers. Use this to "publish" the current frame.
     * @return false on failure
     */
    internal fun swapSurfaceBuffers(eglSurface: EglSurface): Boolean {
        return eglSwapBuffers(eglDisplay, eglSurface)
    }

    /**
     * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
     */
    internal fun setSurfacePresentationTime(eglSurface: EglSurface, nsecs: Long) {
        eglPresentationTime(eglDisplay, eglSurface, nsecs)
    }

    /**
     * Returns true if our context and the specified surface are current.
     */
    internal fun isSurfaceCurrent(eglSurface: EglSurface): Boolean {
        return eglContext == eglGetCurrentContext()
                && eglSurface == eglGetCurrentSurface(EGL_DRAW)
    }

    /**
     * Performs a simple surface query.
     */
    internal fun querySurface(eglSurface: EglSurface, what: Int): Int {
        val value = IntArray(1)
        eglQuerySurface(eglDisplay, eglSurface, what, value)
        return value[0]
    }

    public companion object {
        /**
         * Constructor flag: surface must be recordable.  This discourages EGL from using a
         * pixel format that cannot be converted efficiently to something usable by the video
         * encoder.
         */
        internal const val FLAG_RECORDABLE = 0x01

        /**
         * Constructor flag: ask for GLES3, fall back to GLES2 if not available.  Without this
         * flag, GLES2 is used.
         */
        internal const val FLAG_TRY_GLES3 = 0x02
    }
}

4. 修改transform

这里的mTextureDrawerGlTextureDrawerGlTextureDrawer是一个绘制的管理类,无论是GlCameraPreview(预览)还是SnapshotGlPictureRecorder(带滤镜拍照),都是调用GlTextureDrawer.draw()来渲染openGL的。

public class GlTextureDrawer {
	//...省略了不重要的代码...

    private final GlTexture mTexture;
    private float[] mTextureTransform = Egloo.IDENTITY_MATRIX.clone();

    public void draw(final long timestampUs) {
        //...省略了不重要的代码...
        
        if (mProgramHandle == -1) {
            mProgramHandle = GlProgram.create(
                    mFilter.getVertexShader(),
                    mFilter.getFragmentShader());
            mFilter.onCreate(mProgramHandle);
        }

        GLES20.glUseProgram(mProgramHandle);
        mTexture.bind();
        mFilter.draw(timestampUs, mTextureTransform);
        mTexture.unbind();
        GLES20.glUseProgram(0);
    }

    public void release() {
        if (mProgramHandle == -1) return;
        mFilter.onDestroy();
        GLES20.glDeleteProgram(mProgramHandle);
        mProgramHandle = -1;
    }
}

transform ,也就是mTextureTransform,会传到Filter.draw()中,最终会改变OpenGL绘制的坐标矩阵,也就是GLSL中的uMVPMatrix变量。
而这边就是修改transform 的值,从而对图像进行镜像、旋转等操作。

final float[] transform = mTextureDrawer.getTextureTransform();

// 2. Apply preview transformations
surfaceTexture.getTransformMatrix(transform);
float scaleTranslX = (1F - scaleX) / 2F;
float scaleTranslY = (1F - scaleY) / 2F;
Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0);
Matrix.scaleM(transform, 0, scaleX, scaleY, 1);

// 3. Apply rotation and flip
 // If this doesn't work, rotate "rotation" before scaling, like GlCameraPreview does.
 Matrix.translateM(transform, 0, 0.5F, 0.5F, 0); // Go back to 0,0
 Matrix.rotateM(transform, 0, rotation + mResult.rotation, 0, 0, 1); // Rotate to OUTPUT
 Matrix.scaleM(transform, 0, 1, -1, 1); // Vertical flip because we'll use glReadPixels
 Matrix.translateM(transform, 0, -0.5F, -0.5F, 0); // Go back to old position

5. 绘制Overlay

这个没有研究过,似乎是用来绘制覆盖层。这不重要,这里跳过,一般也不会进入这个逻辑。

// 4. Do pretty much the same for overlays
if (mHasOverlay) {
    // 1. First we must draw on the texture and get latest image
    mOverlayDrawer.draw(Overlay.Target.PICTURE_SNAPSHOT);

    // 2. Then we can apply the transformations
    Matrix.translateM(mOverlayDrawer.getTransform(), 0, 0.5F, 0.5F, 0);
    Matrix.rotateM(mOverlayDrawer.getTransform(), 0, mResult.rotation, 0, 0, 1);
    Matrix.scaleM(mOverlayDrawer.getTransform(), 0, 1, -1, 1); // Vertical flip because we'll use glReadPixels
    Matrix.translateM(mOverlayDrawer.getTransform(), 0, -0.5F, -0.5F, 0);
}
mResult.rotation = 0;

6. 绘制并保存

这里就是带滤镜拍照部分,核心中的核心代码了。
这里主要分为两步

  • mTextureDrawer.draw : 绘制滤镜
  • eglSurface.toByteArray : 将画面保存为JPEG格式的Byte数组
// 5. Draw and save
long timestampUs = surfaceTexture.getTimestamp() / 1000L;
LOG.i("takeFrame:", "timestampUs:", timestampUs);
mTextureDrawer.draw(timestampUs);
if (mHasOverlay) mOverlayDrawer.render(timestampUs);
mResult.data = eglSurface.toByteArray(Bitmap.CompressFormat.JPEG);

这部分具体的代码具体详见下篇文章

7. 释放资源

// 6. Cleanup
eglSurface.release();
mTextureDrawer.release();
fakeOutputSurface.release();
if (mHasOverlay) mOverlayDrawer.release();
core.release();
dispatchResult();

8. 其他

8.1 解决CameraView滤镜黑屏系列

Android 解决CameraView叠加2个以上滤镜拍照黑屏的BUG (一)_氦客的博客-CSDN博客
Android 解决CameraView叠加2个以上滤镜拍照黑屏的BUG (二)_氦客的博客-CSDN博客
Android 解决CameraView叠加2个以上滤镜拍照黑屏的BUG (三)_氦客的博客-CSDN博客

8.2 Android Camera2 系列

更多Camera2相关文章,请看
十分钟实现 Android Camera2 相机预览_氦客的博客-CSDN博客
十分钟实现 Android Camera2 相机拍照_氦客的博客-CSDN博客
十分钟实现 Android Camera2 视频录制_氦客的博客-CSDN博客

8.3 Android 相机相关文章

Android 使用CameraX实现预览/拍照/录制视频/图片分析/对焦/缩放/切换摄像头等操作_氦客的博客-CSDN博客
Android 从零开发一个简易的相机App_android开发简易app_氦客的博客-CSDN博客
Android 使用Camera1实现相机预览、拍照、录像_android 相机预览_氦客的博客-CSDN博客

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

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

相关文章

C++项目案例圆和点的关系 (涉及知识点:头文件定义类,cpp文件实现类,类和作用域,linux编译运行c++项目)

一.项目描述 点与圆有三种关系&#xff1a; 点在圆外 点在圆上 点在圆内计算点到圆心的距离就能判断点在圆的哪个地方。二.项目结构 三.include文件 3.1 Circle类的声明 Circle.h // 防止头文件重复包含 #pragma once // #include<iostream> #include "Point.h&…

PS学习笔记——图层

文章目录 图层面板图层类型新建图层新建方式图层颜色 操作图层修改图层名称选中图层隐藏图层调整图层顺序复制图层 图层面板 按F7可打开/关闭图层面板 该面板就是图层面板了 对所有图层进行筛选的按钮&#xff0c;第一个搜索框可以选择按什么方式进行筛选&#xff0c;支持&am…

Python语言这么火热,其实具有以下特点

Python语言具有以下特点&#xff1a; 简单易学&#xff1a;Python语言是一种解释型语言&#xff0c;语法简单明了&#xff0c;代码简洁&#xff0c;易于理解&#xff0c;可以一边编码一边运行&#xff0c;非常合适编程初学者。门槛较低&#xff1a;Python不需要复杂的环境配置…

使用ADS进行serdes仿真时,Tx_Diff中EQ的设置对发送端波形的影响。

研究并记录一下ADS仿真中Tx_Diff的EQ设置。原理图如下&#xff1a; 最上面是选择均衡方法Choose equalization method&#xff1a;Specify FIR taps&#xff0c;Specify de-emphasis和none。 当选择Specify de-emphasis选项时&#xff0c;下方可以输入去加重具体的dB值&#x…

嵌入式 Linux 移植与系统启动方法

1、Linux系统启动与U-Boot 所谓移植就是把程序代码从一种运行环境转移到另一种运行环境。对于内核移植来说&#xff0c;主要是从一种硬件平台转移到另一种硬件平台上运行。 体系结构级别的移植是指在不同体系结构平台上Linux内核的移植&#xff0c;例如&#xff0c;在ARM、MI…

buildadmin+tp8表格操作(1)----表头上方添加按钮和自定义按钮

buildAdmin 的表头上添加一些按钮&#xff0c;并实现功能 添加按钮 <template><!-- buttons 属性定义了 TableHeader 本身支持的顶部按钮&#xff0c;仅需传递按钮名即可 --><!-- 这里的框架自带的 顶部按钮 分别有 刷新 &#xff0c; 添加&#xff0c; 编辑&…

FPGA模块——IIC协议(读写PCF8591)

FPGA模块——IIC协议&#xff08;读取PCF8591&#xff09; PCF8591/AT8591芯片对iic协议的使用 PCF8591/AT8591芯片 低功耗8位CMOS数据采集设备&#xff0c;4路模拟输入&#xff0c;1路模拟输出&#xff0c;分时多路复用&#xff0c;读取数据用串型iic总线接口&#xff0c;最大…

SFP-10G-SR光模块指南

SFP-10G-SR通常是思科&#xff08;Cisco&#xff09;使用的型号名。是一种用于非常短距离应用的最低成本、最低功耗的10G SFP模块。本文汇总了初学者在第一阶段关于10G SFP SR模块的常见问题。 SFP-10G-SR模块是否支持GE&#xff1f; 10GBASE-SR模块本身是可以支持GE速度的&am…

mysql客户端navicat的一些错误合集

关于mysql的客户端的使用的一些问题 问题描述&#xff1a; 在使用navicat prenium客户端的时候&#xff0c;连接数据库出现 Table ‘performance_schema.session_variables’ doesn’t exist 错误 解决方案&#xff1a; 首先找到mysql的bin目录 然后winR 进入到cmd界面 输入…

基于单片机音乐弹奏播放DS1302万年历显示及源程序

一、系统方案 1、本设计采用51单片机作为主控器。 2、DS1302计时显示年月日时分秒。 3、按键可以弹奏以及播放音乐&#xff0c;内置16首音乐。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /时钟显示**/ void init_1602_ds1302() { write…

JS判断是否存在某个元素(includes、indexOf、find、findeIndex、some)(every 数组内所有值是否相同)

方法一&#xff1a;array.includes(searcElement[,fromIndex]) 此方法判断数组中是否存在某个值&#xff0c;如果存在返回true&#xff0c;否则返回false。 searchElement&#xff1a;需要查找的元素&#xff0c;必选。fromIndex&#xff1a;可选&#xff0c;从该索引处开始查…

挑战视觉边界,探索图形验证码背后的黑科技

在日常生活中&#xff0c;我们登录网站或者其他平台时&#xff0c;在填写完账号密码之后&#xff0c;还会让我们填写4或6位的数字或者英文字母等&#xff0c;填写正确才能请求登录。这个其实是防止某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试&#xff0c;如下…

【LearnOpenGL基础入门——3】绘制纯色三角形

目录 一.写在前面 二.顶点输入 三.顶点着色器 四.编译着色器 五.片段着色器 六.着色器程序 七.链接顶点属性 彩蛋 一.写在前面 我们先认识一下OpenGL常用的几个名词&#xff1a; 顶点数组对象&#xff1a;Vertex Array Object&#xff0c;VAO顶点缓冲对象&#xff1a;…

Unity之NetCode多人网络游戏联机对战教程(9)--NetworkAnimator组件

文章目录 前言NetworkAnimatorAnimator的Trigger属性服务器权威模式&#xff08;Server Authoritative Mode&#xff09;客户端权威模式 (Owner Authoritative Mode)学习文档 前言 这个组件是NetCode常用的组件之一&#xff0c;NetworkAnimator跟NetworkTransform一样&#xf…

VSG-001

VulkanSceneGraph (VSG), is a modern, cross platform, high performance scene graph library built upon Vulkan VSG 是一个基于vulkan的现代的、跨平台的高性能场景管理库 VSg特性&#xff1a; 使用C17作为c规范编码&#xff0c;支持 CppCoreGuidelines支持 FOSS Best P…

Google Chrome 任意文件读取 (CVE-2023-4357)漏洞复现

Google Chrome 任意文件读取 (CVE-2023-4357)漏洞复现 1.漏洞描述 该漏洞的存在是由于 Google Chrome 中用户提供的 XML 输入验证不足。远程攻击者可以创建特制网页&#xff0c;诱骗受害者访问该网页并获取用户系统上的敏感信息。远程攻击者可利用该漏洞通过构建的 HTML 页面…

Leetcode——最长递增子序列

1. 题目链接&#xff1a;300. 最长递增子序列 2. 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&a…

基于秃鹰算法优化概率神经网络PNN的分类预测 - 附代码

基于秃鹰算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于秃鹰算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于秃鹰优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

Android Fragment 要你何用?2.0版本

作者&#xff1a;小鱼人爱编程 1. 老生常谈&#xff1a;为什么需要Fragment? 先看Activity、Fragment、View三者的关系&#xff1a; Activity 拥有生命周期&#xff0c;但是需要和AMS通信(跨进程)&#xff0c;比较臃肿。 View 不需要和AMS通信&#xff0c;但没有生命周期&…

机器学习第9天:决策树分类

文章目录 机器学习专栏 介绍 基本思想 使用代码 深度探索 优点 估计概率 训练算法 CART成本函数 实例数与不纯度 正则化 在鸢尾花数据集上训练决策树 机器学习专栏 机器学习_Nowl的博客-CSDN博客 介绍 作用&#xff1a;分类 原理&#xff1a;构建一个二叉树&#…