OpenGL 入门(四)—— 贴纸与美颜滤镜

本篇我们来介绍贴纸效果与美颜滤镜的实现。

1、贴纸效果

贴纸实际上是一个图片,用 Bitmap 加载图片后用 OpenGL 渲染到指定的位置上。我们举例添加一个耳朵贴纸:

erduo_000

1.1 获取人脸位置

上一篇我们在讲大眼滤镜时,在 Native 层除了获取到人脸 5 个特征点的坐标之外,还保存了人脸起始点坐标与宽高,这个数据其实是我们添加贴纸时才会用到的:

void FaceTracker::detect(const Mat &src, std::vector<Rect2f> &rectangles) {
    std::vector<Rect> faces;
    tracker->process(src);
    tracker->getObjects(faces);

    if (!faces.empty()) {
        // 先只处理一个人脸,将其位置信息保存到 rectangles 中备用
        Rect face = faces[0];
        // 保存人脸起始点(左上角)以及宽高
        rectangles.emplace_back(face.x, face.y, face.width, face.height);

        seeta::ImageData imageData = seeta::ImageData(src.cols, src.rows);
        imageData.data = src.data;

        seeta::FaceInfo faceInfo;
        seeta::Rect bbox;
        bbox.x = face.x;
        bbox.y = face.y;
        bbox.width = face.width;
        bbox.height = face.height;
        faceInfo.bbox = bbox;

        seeta::FacialLandmark landmarks[5];

        faceAlignment->PointDetectLandmarks(imageData, faceInfo, landmarks);

        for (auto & landmark : landmarks) {
            rectangles.emplace_back(landmark.x, landmark.y, 0, 0);
        }
    }
}

该数据最终被封装到 Face 中,通过 landmarks[0]、landmarks[1] 可以获取起始点坐标,通过 faceWidth、faceHeight 获取人脸宽高。

1.2 贴纸滤镜实现

添加贴纸不需要增加新的着色器,大致步骤如下:

  1. 用 Bitmap 加载贴纸资源,在准备阶段创建一个贴纸纹理与该 Bitmap 绑定
  2. 绘制阶段先将贴纸滤镜前面的滤镜绘制的内容绘制出来,然后再绘制贴纸
  3. 绘制贴纸时,重要的是计算好贴纸的起始位置与宽高并设置给 glViewport()

绘制贴纸之前的代码是常规套路前面已经说过多次,这里直接贴出,不再赘述:

class StickFilter(context: Context) :
    BaseFrameFilter(context, R.raw.base_vertex, R.raw.base_fragment) {

    private val mBitmap: Bitmap

    // 绘制贴纸的纹理
    private lateinit var mTextureId: IntArray
    private var mFace: Face? = null

    init {
        mBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.erduo_000)
    }

    override fun initCoordinator() {
        // 转 180° 调正
        val texture = floatArrayOf(
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f
        )
        mTextureBuffer.clear()
        mTextureBuffer.put(texture)
    }

    override fun onReady(width: Int, height: Int) {
        super.onReady(width, height)

        // 1.生成并绑定贴纸的纹理 ID
        mTextureId = IntArray(1)
        TextureHelper.generateTextures(mTextureId)
        glBindTexture(GL_TEXTURE_2D, mTextureId[0])

        // 2.将 Bitmap 的像素数据加载到 OpenGL 的纹理对象中
        GLUtils.texImage2D(GL_TEXTURE_2D, 0, mBitmap, 0)

        // 3.解绑
        glBindTexture(GL_TEXTURE_2D, 0)
    }

    override fun onDrawFrame(textureId: Int): Int {
        // 1.如果数据不足无法绘制贴纸,就返回上一层的纹理 ID
        val landmarks = mFace?.landmarks
        val imgWidth = mFace?.imgWidth ?: 0
        val imgHeight = mFace?.imgHeight ?: 0
        if (landmarks == null || imgWidth == 0 || imgHeight == 0) {
            return textureId
        }

        // 2.渲染前的设置
        // 2.1 设置视窗
        glViewport(0, 0, mWidth, mHeight)

        // 2.2 绑定 FBO
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers!![0])

        // 2.3 使用着色器程序
        glUseProgram(mProgramId)

        // 3.给顶点着色器的顶点和纹理坐标变量传值
        mVertexBuffer.position(0)
        glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer)
        glEnableVertexAttribArray(vPosition)

        mTextureBuffer.position(0)
        glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer)
        glEnableVertexAttribArray(vCoord)

        // 4.绘制前面滤镜的内容
        // 4.1 激活图层
        glActiveTexture(GL_TEXTURE0)

        // 4.2 绑定纹理
        glBindTexture(GL_TEXTURE_2D, textureId)

        // 4.3 给采样器传参
        glUniform1i(vTexture, 0)

        // 4.4 通知 OpenGL 绘制
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)

        // 4.5 解绑 FBO
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindTexture(GL_TEXTURE_2D, 0)

        // 5.绘制贴纸
        drawStick(landmarks, imgWidth, imgHeight)

        return mFrameBufferTextures!![0]
    }
}

主要看绘制贴纸的方法 drawStick():

	private fun drawStick(landmarks: FloatArray, imgWidth: Int, imgHeight: Int) {
        // 1.混合模式
        // 1.1 开启混合模式
        glEnable(GL_BLEND)
        // 1.2 设置混合模式:
        // GL_ONE 表示原图全部绘制
        // GL_ONE_MINUS_SRC_ALPHA 表示目标图因子 = 1 - 源图 alpha
        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)

        // 2.设置贴纸的绘制区域
        // 2.1 计算人脸起始点坐标(图像内坐标转换为屏幕内坐标)
        val xInScreen = landmarks[0] / imgWidth * mWidth
        val yInScreen = landmarks[1] / imgHeight * mHeight
        // 2.2 设置贴纸绘制的起始点与宽高
        glViewport(
            xInScreen.toInt(),
            // yInScreen 是人脸的起始点纵坐标,而贴纸需要放在头上,向上移适当距离
            (yInScreen - mBitmap.height / 2).toInt(),
            // 贴纸宽度要根据人脸矩形宽度在屏幕内等比例缩放,记得先用 Float 计算否则误差较大
            ((mFace?.faceWidth ?: 0).toFloat() / imgWidth * mWidth).toInt(),
            mBitmap.height
        )

        // 3.绑定 FBO、设置着色器程序
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers!![0])
        glUseProgram(mProgramId)

        // 4.为顶点坐标和纹理坐标赋值
        mVertexBuffer.position(0)
        glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer)
        glEnableVertexAttribArray(vPosition)

        mTextureBuffer.position(0)
        glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer)
        glEnableVertexAttribArray(vCoord)

        // 5.绘制与解绑
        // 激活图层
        glActiveTexture(GL_TEXTURE0)
        // 绑定
        glBindTexture(GL_TEXTURE_2D, mTextureId[0])
        // 传递参数
        glUniform1i(vTexture, 0)
        // 通知 OpenGL 绘制
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
        // 解绑 FBO
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindTexture(GL_TEXTURE_2D, 0)
        // 关闭混合模式
        glDisable(GL_BLEND)
    }

需要注意的就是 2.2 设置绘制的起点与宽高的问题:

  • 如果起始点的纵坐标不向上移动适当距离,那么贴纸就会贴在眼睛上方,而不是头上方
  • 贴纸的宽度需要随着人脸矩形的宽度变化,否则人离屏幕很远的情况下,脸变小了,但贴纸还是原来的大小就很违和

最后将 StickFilter 添加到渲染器的责任链中进行绘制。这里我们做了一点改动,就是在 UI 上添加了各个滤镜的开关,当开启滤镜时,才进行绘制。因此要从 UI 将开启状态经过 FilterSurfaceView 同步给渲染器 GLRender:

	override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBinding.root)

        // 各个滤镜的控制开关
        mBinding.cbBeauty.setOnCheckedChangeListener { _, isChecked ->
            mBinding.myGlSurfaceView.enableBeauty(isChecked)
        }
        mBinding.cbBigEye.setOnCheckedChangeListener { _, isChecked ->
            mBinding.myGlSurfaceView.enableBigEye(isChecked)
        }
        mBinding.cbStick.setOnCheckedChangeListener { _, isChecked ->
            mBinding.myGlSurfaceView.enableStick(isChecked)
        }
    }

FilterSurfaceView 只做简单的传递:

    fun enableBigEye(checked: Boolean) {
        mGLRender.enableBigEye(checked)
    }

    fun enableStick(checked: Boolean) {
        mGLRender.enableStick(checked)
    }

GLRender 收到后要在 OpenGL 的渲染线程中做滤镜对象的创建工作:

	fun enableBigEye(checked: Boolean) {
        mGLSurfaceView.queueEvent {
            if (checked) {
                mBigEyesFilter = BigEyesFilter(mContext)
                // 同步宽高信息
                mBigEyesFilter?.onReady(mWidth, mHeight)
            } else {
                mBigEyesFilter?.release()
                mBigEyesFilter = null
            }
        }
    }

    fun enableStick(checked: Boolean) {
        mGLSurfaceView.queueEvent {
            if (checked) {
                mStickFilter = StickFilter(mContext)
                mStickFilter?.onReady(mWidth, mHeight)
            } else {
                mStickFilter?.release()
                mStickFilter = null
            }
        }
    }

queueEvent() 会将任务提交到 OpenGL ES 渲染线程执行。因为 Android OpenGL ES 的渲染操作必须在渲染线程(也就是 GLSurfaceView 中的 GLThread)上执行,以避免多线程访问 OpenGL ES 上下文导致的竞态条件和不一致性。因此,当我们需要在非渲染线程上执行 OpenGL ES 操作时,就需要使用 queueEvent 来将任务提交到渲染线程执行。

在绘制时也要做相应修改:

	private lateinit var mScreenFilter: ScreenFilter
    private lateinit var mCameraFilter: CameraFilter
    private var mBigEyesFilter: BigEyesFilter? = null
    private var mStickFilter: StickFilter? = null

	override fun onDrawFrame(gl: GL10?) {
        ...
        // 3.交给滤镜进行具体的绘制工作
        mCameraFilter.setMatrix(mMatrix)
        var textureId = mCameraFilter.onDrawFrame(mTextureIds[0])

        mBigEyesFilter?.setFace(mFaceTracker.getFace())
        textureId = mBigEyesFilter?.onDrawFrame(textureId) ?: textureId

        mStickFilter?.setFace(mFaceTracker.getFace())
        textureId = mStickFilter?.onDrawFrame(textureId) ?: textureId

        mScreenFilter.onDrawFrame(textureId)
    }

2、美颜滤镜

美颜滤镜的实现的重点是在美颜算法,算法是写在片元着色器中的,我们先来看其实现。

2.1 美颜着色器

美颜效果主要是通过着色器中的算法代码实现的,算法有很多种,这里我们提供一种算法放在 beauty_fragment.glsl 中:

precision mediump float;

varying mediump vec2 aCoord;

uniform sampler2D vTexture;

// 图片(纹理)宽高
uniform int width;
uniform int height;

// 高斯模糊的 20 个采样点
vec2 blurCoordinates[20];

void main() {
    // 1.高斯模糊
    // 像素点步长
    vec2 singleStepOffset = vec2(1.0 / float(width), 1.0 / float(height));
    // aCoord 是 GPU 当前渲染的像素点, 整个公式就是求出距离当前正在渲染的像素点在
    // X 轴方向左侧 10 个像素单位的点的坐标。后续的采样点也是类似的含义
    blurCoordinates[0] = aCoord.xy + singleStepOffset * vec2(0.0, -10.0);
    blurCoordinates[1] = aCoord.xy + singleStepOffset * vec2(0.0, 10.0);
    blurCoordinates[2] = aCoord.xy + singleStepOffset * vec2(-10.0, 0.0);
    blurCoordinates[3] = aCoord.xy + singleStepOffset * vec2(10.0, 0.0);
    blurCoordinates[4] = aCoord.xy + singleStepOffset * vec2(5.0, -8.0);
    blurCoordinates[5] = aCoord.xy + singleStepOffset * vec2(5.0, 8.0);
    blurCoordinates[6] = aCoord.xy + singleStepOffset * vec2(-5.0, 8.0);
    blurCoordinates[7] = aCoord.xy + singleStepOffset * vec2(-5.0, -8.0);
    blurCoordinates[8] = aCoord.xy + singleStepOffset * vec2(8.0, -5.0);
    blurCoordinates[9] = aCoord.xy + singleStepOffset * vec2(8.0, 5.0);
    blurCoordinates[10] = aCoord.xy + singleStepOffset * vec2(-8.0, 5.0);
    blurCoordinates[11] = aCoord.xy + singleStepOffset * vec2(-8.0, -5.0);
    blurCoordinates[12] = aCoord.xy + singleStepOffset * vec2(0.0, -6.0);
    blurCoordinates[13] = aCoord.xy + singleStepOffset * vec2(0.0, 6.0);
    blurCoordinates[14] = aCoord.xy + singleStepOffset * vec2(6.0, 0.0);
    blurCoordinates[15] = aCoord.xy + singleStepOffset * vec2(-6.0, 0.0);
    blurCoordinates[16] = aCoord.xy + singleStepOffset * vec2(-4.0, -4.0);
    blurCoordinates[17] = aCoord.xy + singleStepOffset * vec2(-4.0, 4.0);
    blurCoordinates[18] = aCoord.xy + singleStepOffset * vec2(4.0, -4.0);
    blurCoordinates[19] = aCoord.xy + singleStepOffset * vec2(4.0, 4.0);

    // 正在渲染(采样)的点,即所有采样点的中心点的颜色矩阵
    vec4 currentColor = texture2D(vTexture, aCoord);

    // 计算 21 个点的颜色总和
    vec3 totalRGB = currentColor.rgb;
    for (int i = 0; i < 20; i++) {
        totalRGB += texture2D(vTexture, blurCoordinates[i].xy).rgb;
    }

    vec4 blur = vec4(totalRGB * 1.0 / 21.0, currentColor.a);

    // 2.高反差保留
    // 用原图减去高斯模糊的图
    // https://shaderific.com/glsl/common_functions.html
    // OpenGL 内置函数参考网站
    vec4 highPassColor = currentColor - blur;
    // clamp 会返回三个参数中大小在中间的那个数
    // 计算强度系数,对每个颜色通道取反向
    highPassColor.r = clamp(2.0 * highPassColor.r * highPassColor.r * 24.0, 0.0, 1.0);
    highPassColor.g = clamp(2.0 * highPassColor.g * highPassColor.g * 24.0, 0.0, 1.0);
    highPassColor.b = clamp(2.0 * highPassColor.b * highPassColor.b * 24.0, 0.0, 1.0);

    vec4 highPassBlur = vec4(highPassColor.rgb, 1.0);

    // 3.磨皮(融合)
    // 蓝色分量
    float blue = min(currentColor.b, blur.b);
    float value = clamp((blue - 0.2) * 5.0, 0.0, 1.0);

    // 取 RGB 三个分量重最大的值
    float maxChannelColor = max(max(currentColor.r, currentColor.g), currentColor.b);

    // 磨皮强度
    float intensity = 1.0;
    float currentIntensity = (1.0 - maxChannelColor / (maxChannelColor + 0.2)) * value * intensity;

    // mix 返回线性混合的 xy,如 x(1 - a) + ya
    vec3 r = mix(currentColor.rgb, blur.rgb, currentIntensity);

    gl_FragColor = vec4(r, 1.0);
}

2.2 美颜滤镜

滤镜代码前面已经添加过几次,都是固定套路了:

/**
 * 美颜:反向、高反差保留、高斯模糊
 */
class BeautyFilter(context: Context) :
    BaseFrameFilter(context, R.raw.base_vertex, R.raw.beauty_fragment) {

    // 着色器中定义的宽高变量
    private val width: Int = glGetUniformLocation(mProgramId, "width")
    private val height: Int = glGetUniformLocation(mProgramId, "height")


    override fun initCoordinator() {
        val texture = floatArrayOf(
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f
        )
        mTextureBuffer.clear()
        mTextureBuffer.put(texture)
    }

    override fun onDrawFrame(textureId: Int): Int {
        // 1.设置视窗
        glViewport(0, 0, mWidth, mHeight)
        // 绑定 FBO
        glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers!![0])

        // 2.使用着色器程序
        glUseProgram(mProgramId)

        // 3.为着色器中定义的变量赋值
        mVertexBuffer.position(0)
        glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer)
        glEnableVertexAttribArray(vPosition)

        mTextureBuffer.position(0)
        glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer)
        glEnableVertexAttribArray(vCoord)

        glUniform1i(width, mWidth)
        glUniform1i(height, mHeight)

        // 4.后续常规操作,OpenGL 绘制
        // 激活图层
        glActiveTexture(GL_TEXTURE0)
        // 绑定
        glBindTexture(GL_TEXTURE_2D, textureId)
        // 传递参数
        glUniform1i(vTexture, 0)
        // 通知 OpenGL 绘制
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
        // 解绑 FBO
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindTexture(GL_TEXTURE_2D, 0)

        return mFrameBufferTextures!![0]
    }
}

最后在渲染器中添加美颜滤镜:

	private var mBeautyFilter: BeautyFilter? = null
	
    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        ...
        mBeautyFilter?.onReady(width, height)
        ...
    }

    override fun onDrawFrame(gl: GL10?) {
        ...
        // 3.交给滤镜进行具体的绘制工作
        mCameraFilter.setMatrix(mMatrix)
        var textureId = mCameraFilter.onDrawFrame(mTextureIds[0])

        mBigEyesFilter?.setFace(mFaceTracker.getFace())
        textureId = mBigEyesFilter?.onDrawFrame(textureId) ?: textureId

        mStickFilter?.setFace(mFaceTracker.getFace())
        textureId = mStickFilter?.onDrawFrame(textureId) ?: textureId

        textureId = mBeautyFilter?.onDrawFrame(textureId) ?: textureId

        mScreenFilter.onDrawFrame(textureId)
    }

	fun enableBeauty(checked: Boolean) {
        mGLSurfaceView.queueEvent {
            if (checked) {
                mBeautyFilter = BeautyFilter(mContext)
                // 同步宽高信息
                mBeautyFilter?.onReady(mWidth, mHeight)
            } else {
                mBeautyFilter?.release()
                mBeautyFilter = null
            }
        }
    }

结果如文章开头演示所示,至此,滤镜系列完结。

参考资料:

高反差保留算法: https://www.jianshu.com/p/bb702124d2ad

图层混合强光模式:https://blog.csdn.net/matrix_space/article/details/22426633

开源美颜相机工程参考:https://github.com/wuhaoyu1990/MagicCamera

美白着色器代码参考:https://github.com/smzhldr/AGLFramework/blob/master/aglframework/src/main/res/raw/light_f.glsl

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

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

相关文章

JAVA学习笔记(第三周)

文章目录 继承概述使用场景继承的特点子类继承的内容成员变量访问特点成员方法访问特点方法的重写构造方法this super 多态多态的表现形式多态的前提成员变量和方法调用instanceof优势弊端 包包名的规则全类名final常量 权限修饰符代码块 继承 概述 继承就是子类继承父类的特征…

PHP ASCII码的字符串用mb_convert_encoding 转utf-8之后不生效

检测数据类型是ascii,转码之后再检测还是utf-8没生效 private function toUTF8($str){$encode mb_detect_encoding($str, array("ASCII",UTF-8,"GB2312","GBK",BIG5,LATIN1));if ($encode ! UTF-8) {$str1 mb_convert_encoding($str, UTF-8, …

(2024,LSTM,Transformer,指数门控,归一化器状态,多头内存混合)xLSTM:扩展的 LSTM

xLSTM: Extended Long Short-Term Memory 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 简介 2. 扩展的 LSTM 2.1 LSTM 回顾 2.2 sLSTM 2.3 mLSTM 2.4 xLSTM 架构 2…

python:画饼图

我现在因工作需要在写一篇中文文章&#xff0c;领导要我用python处理数据和画图&#xff0c;那我也刚好学习一下python画图。 import matplotlib.pyplot as plt # 饼图数据 labels [A, B, C, D] sizes [15, 30, 45, 10] # 每个部分的大小 # 绘制饼图 plt.figure(figsize(6,…

读书笔记——《高质量C++/C编程指南》(2)

目录 前言 命名规则 共性规则 简单的Windows应用程序命名规则 表达式和基本语句 运算符优先级 复合表达式 if语句 布尔变量与零值比较 整型变量与零值比较 浮点变量与零值比较 指针变量与零值比较 对if 语句的补充说明 循环语句的效率 for 语句的循环控制变量 s…

数据库大作业——基于qt开发的图书管理系统(四)项目目录的整理与绘制登录页面

项目目录的管理 前言 在上几篇的文章里面我们完成了基本环境的搭建,整理了项目数据库表结构并且成功的手动的加载了Qt的mysql数据库驱动&#xff0c;现在就要开始完成项目准备工作的最后一步:构建项目目录,一个好的项目离不开一个好的代码组织结构,所以在开始动手写我们这个项…

Java | Leetcode Java题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; public class Solution {public int climbStairs(int n) {double sqrt5 Math.sqrt(5);double fibn Math.pow((1 sqrt5) / 2, n 1) - Math.pow((1 - sqrt5) / 2, n 1);return (int) Math.round(fibn / sqrt5);} }

无人机+通信中继:短波电台技术详解

随着无线通信技术的不断发展&#xff0c;无人机作为一种新型的信息传输平台&#xff0c;已经在多个领域得到了广泛应用。其中&#xff0c;无人机与短波电台的结合&#xff0c;为通信中继领域带来了全新的可能性。本文将详细解析无人机在通信中继中的应用&#xff0c;以及短波电…

产品专访|“产品”远程运维系统与“设备”远程运维系统的区别?

在日益复杂的工业制造环境下&#xff0c;远程运维已经成为生产制造企业不可或缺的一部分。在这个大背景下&#xff0c;产品远程运维系统和设备远程运维系统的需求越来越多&#xff0c;各自发挥着独特的作用。然而&#xff0c;尽管它们都涉及到远程运维的概念&#xff0c;但在实…

Nest.js中使用任务调度

java中的xxl在nestJs中是有内置的任务调度nestjs/schedule npm install --save nestjs/schedule 在model中引入使用 在service中直接使用就行 具体间隔多久看官方配置 Task Scheduling | NestJS 中文文档 | NestJS 中文网

STM32F1#1(入门了解)

一、STM32开发平台和工具 1.1 STM32芯片介绍 典型微控制器由CPU&#xff08;运算器、控制器&#xff09;、RAM、ROM和输入输出组成。 1.2 STM32核心板 STM32核心板配件&#xff1a; ①JTAG/SWD仿真-下载器 ②通信-下载模块 ③OLED显示屏 1&#xff09; 通信-下载模…

智慧工厂管理系统

随着科技的飞速发展&#xff0c;传统工厂正经历着一场前所未有的变革。在这个以智能化、信息化为主导的新时代&#xff0c;HiWoo Cloud平台以其卓越的智慧工厂管理系统&#xff0c;成为了众多企业转型升级的首选工具。今天&#xff0c;就让我们一起走进HiWoo Cloud的世界&#…

FTTR(光猫)ITMS注册NCE纳管

ITMS注册 TR069交互过程&#xff1a; 1.1. TR069交互—主动连接机制 主动连接机制是指CPE主动发出请求连接事件(事件可以为&#xff1a; 0 BOOTSTRAP&#xff1b; 1 BOOT; PERIODIC等等)给ACS。在连接建立之后才能进行业务处理(通过调用RPC方法实现)。 备注&#xff1a;政企…

【2024最新华为OD-C卷试题汇总】字符串分割(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; 文章目录 前…

2024.5.8

聊天框完善 #include "mywidget.h" #include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget) {ui->setupUi(this);//设置窗口大小this->resize(400,560);//设置窗口图标和标题this->setWindowTit…

快过VS Code,10天暴增20k star,高性能多人协作IDE横空出世

道歉 其实不意味着道歉的人错了 而是他认为这段关系 比自己的尊严更重要 失败了 不是说你有多差 而是说 你需要更努力了 写代码最重要的一个选择就是选哪个IDE了&#xff0c;目前主流的选择是vscode和IDEA了。 但是vscode虽然轻量&#xff0c;但是对于大型的项目仍然显得…

C语言----杨辉三角

各位看官们好。学习到这里想必大家应该对C语言的了解也是很深刻的了吧。但是我们也不能忘记我们一起学习的知识啊。在我们以前学习C语言的时候我想大家应该都听说过杨辉三角吧。虽然我们把其中的规律找到那么这个代码就简单很多了。那么接下里我们就来讲讲杨辉三角。 首先我们先…

实战28套JAVA高端架构P6/P7/P8架构—全栈架构

概述 Java SE Java SE&#xff08;Java Platform&#xff0c;Standard Edition&#xff09;。Java SE 以前称为J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的Java应用程序。Java SE 包含了支持Java Web 服务开发的类&#xff0c;并为Java Platform&…

Web服务器和Tomcat

Web介绍 对于http协议操作进行封装、简化web程序开发 部署web项目&#xff0c;对外提供上网信息浏览 Tomcat介绍 一个轻量级的web服务器 也称为web容器 Tomcat的文件夹介绍 下载地址&#xff1a;Apache Tomcat - Apache Tomcat 9 Software Downloads 安装&#xff1a;直…

「YashanDB迁移体验官」Oracle向YashanDB迁移的丝滑体验

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…