Android 使用OpenGLES + MediaPlayer 获取视频截图

news2024/12/1 8:21:57

在这里插入图片描述

概述

    Android 获取视频缩略图的方法通常有:

  1. ContentResolver: 使用系统数据库
  2. MediaMetadataRetriever: 这个是android提供的类,用来获取本地和网络media相关文件的信息
  3. ThumbnailUtils: 是在android2.2(api8)之后新增的一个,该类为我们提供了三个静态方法供我们使用。
  4. MediaExtractor 与MediaMetadataRetriever类似
    然而, 这几种方法存在一定的局限性, 比如, ContentResolver需要视频文件已经通过mediascanner 添加到系统数据库中, 使用MediaMetadataRetriever不支持某些格式等等. 常规的格式比如MP4, MKV, 这些接口还是很实用的.

    对于系统不支持的播放的格式比如AVI等, 需要一个更丰富的接口或方法来获取视频的缩略图. 于是尝试使用OpenGLES 离屏渲染 + MediaPlayer来提取视频画面作为缩略图.

参考代码

参考:ExtractMpegFramesTest.java 改动, 使用MediaPlayer, 理论上, 只要使用MediaPlayer可以播放的视频, 都可以提取出视频画面.


import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES10;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.os.Build;
import android.util.Log;
import android.view.Surface;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.microedition.khronos.opengles.GL10;

public class VideoFrameExtractorGL extends Thread implements MediaPlayer.OnSeekCompleteListener {
	final String TAG = "VFEGL";
	private MediaPlayer mediaPlayer;
	private Surface surface;
	private int bitmapWidth, bitmapHeight;

	private int textureId;
	private SurfaceTexture surfaceTexture;

	final Object drawLock = new Object();

	// Shader代码
	private String vertexShaderCode =
			"#version 300 es\n" +
					"in vec4 position;\n" +
					"in vec2 texCoord;\n" +
					"uniform mat4 uSTMatrix;\n" +
					"out vec2 vTexCoord;\n" +
					"\n" +
					"void main() {\n" +
					"    // \n" +
					"    float curvature = -0.5; // 曲率值,负值表示凹面\n" +
					"    vec4 pos = position;\n" +
					"    //pos.z = curvature * (pos.x * pos.x + pos.y * pos.y);\n" +
					"\n" +
					"    //if(pos.x > 0.0001) pos.y += 0.2;\n" +
					"\n" +
					"    gl_Position = pos;\n" +
					"    vTexCoord = (uSTMatrix * vec4(texCoord, 0.0, 1.0)).xy;\n" +
					"}";

	private String fragmentShaderCode =
			"#version 300 es\n" +
					"#extension GL_OES_EGL_image_external : require\n" +
					"precision mediump float;\n" +
					"\n" +
					"in vec2 vTexCoord;\n" +
					"uniform samplerExternalOES sTexture;\n" +
					"out vec4 fragColor;\n" +
					"\n" +
					"void main() {\n" +
					"    fragColor = texture(sTexture, vTexCoord);\n" +
					"}\n";

	protected float[] mSTMatrix = new float[16];
	protected int mProgram;
	private int mPositionHandle;
	private int mTexCoordHandle;
	private int mSTMatrixHandle;

	// 顶点坐标和纹理坐标
	private final float[] squareCoords = {
			-1.0f,  1.0f,   // top left
			1.0f,  1.0f,   // top right
			-1.0f, -1.0f,   // bottom left
			1.0f, -1.0f    // bottom right
	};

	private final float[] textureCoords = {
			0.0f, 0.0f,   // top left
			1.0f, 0.0f,   // top right
			0.0f, 1.0f,   // bottom left
			1.0f, 1.0f    // bottom right
	};

	private FloatBuffer vertexBuffer;
	private FloatBuffer textureBuffer;


	// EGL相关变量
	private EGLDisplay eglDisplay;
	private EGLContext eglContext;
	private EGLSurface eglSurface;

	boolean isPrepared = false;
	long videoDuration = 0;
	int videoWidth, videoHeight;
	String mPath;

	// 构造函数,初始化MediaPlayer并设置渲染大小
	public VideoFrameExtractorGL(String videoFile, int bitmapWidth, int bitmapHeight, OnFrameExtractListener l) {
		this.bitmapWidth = bitmapWidth;
		this.bitmapHeight = bitmapHeight;
		mPath = videoFile;
		setOnFrameExtractListener(l);
	}

	@Override
	public void run() {
		mediaPlayer = new MediaPlayer();
		mediaPlayer.setVolume(0, 0);
		mediaPlayer.setOnSeekCompleteListener(this);

		initializeEGL();   // 初始化EGL环境
		initializeOpenGL(); // 初始化OpenGL离屏渲染环境
		try {
			mediaPlayer.setDataSource(mPath);
			mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
				@Override
				public void onPrepared(MediaPlayer mediaPlayer) {
					Log.d(TAG, "onPrepared start playing");
					isPrepared = true;
					videoDuration = mediaPlayer.getDuration();
					videoWidth = mediaPlayer.getVideoWidth();
					videoHeight = mediaPlayer.getVideoHeight();
					mediaPlayer.start();
				}
			});
			mediaPlayer.prepareAsync();

		} catch (Exception e) {
			e.printStackTrace();
		}

		int timeout = 20;
		while(!isPrepared){
			try {
				sleep(30);
				timeout--;
				if(timeout < 0){
					break;
				}
			} catch (InterruptedException ignore) {}
		}

		while(mediaPlayer != null) {
			drawFrameLoop();
		}
	}

	// 初始化EGL环境
	@SuppressLint("NewApi")
	private void initializeEGL() {
		Log.d(TAG, "initializeEGL");
		// 1. 获取默认的EGL显示设备
		eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
		if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
			throw new RuntimeException("Unable to get EGL14 display");
		}

		// 2. 初始化EGL
		int[] version = new int[2];
		if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
			throw new RuntimeException("Unable to initialize EGL14");
		}

		// 3. 配置EGL
		int[] configAttributes = {
				EGL14.EGL_RED_SIZE, 8,
				EGL14.EGL_GREEN_SIZE, 8,
				EGL14.EGL_BLUE_SIZE, 8,
				EGL14.EGL_ALPHA_SIZE, 8,
				EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
				EGL14.EGL_NONE
		};
		EGLConfig[] eglConfigs = new EGLConfig[1];
		int[] numConfigs = new int[1];
		EGL14.eglChooseConfig(eglDisplay, configAttributes, 0, eglConfigs, 0, 1, numConfigs, 0);
		if (numConfigs[0] == 0) {
			throw new IllegalArgumentException("No matching EGL configs");
		}
		EGLConfig eglConfig = eglConfigs[0];

		// 4. 创建EGL上下文
		int[] contextAttributes = {
				EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
				EGL14.EGL_NONE
		};
		eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttributes, 0);
		if (eglContext == null) {
			throw new RuntimeException("Failed to create EGL context");
		}

		// 5. 创建离屏渲染的EGL Surface
		int[] surfaceAttributes = {
				EGL14.EGL_WIDTH, bitmapWidth,
				EGL14.EGL_HEIGHT, bitmapHeight,
				EGL14.EGL_NONE
		};
		eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttributes, 0);
		if (eglSurface == null) {
			throw new RuntimeException("Failed to create EGL Surface");
		}

		// 6. 绑定上下文
		if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
			throw new RuntimeException("Failed to bind EGL context");
		}
	}

	// 初始化OpenGL ES的离屏渲染,使用帧缓冲区
	private void initializeOpenGL() {
		Log.d(TAG, "initializeOpenGL");
		// 创建纹理并绑定
		int[] textureIds = new int[1];
		GLES20.glGenTextures(1, textureIds, 0);
		textureId = textureIds[0];
		// 绑定纹理并绘制
		//GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
		GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
		GLES20.glTexParameterf(
				GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
				GLES20.GL_NEAREST
		);
		GLES20.glTexParameterf(
				GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
				GLES20.GL_LINEAR
		);
		GLES20.glTexParameteri(
				GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
				GLES20.GL_CLAMP_TO_EDGE
		);
		GLES20.glTexParameteri(
				GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
				GLES20.GL_CLAMP_TO_EDGE
		);

		surfaceTexture = new SurfaceTexture(textureId);
		surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
			@Override
			public void onFrameAvailable(SurfaceTexture surfaceTexture) {
				Log.d(TAG, "onFrameAvailable");
				synchronized (drawLock){
					drawLock.notifyAll();
				}
			}
		});
		// 创建Surface与MediaPlayer绑定
		surface = new Surface(surfaceTexture);
		mediaPlayer.setSurface(surface);

		// 初始化着色器
		createProgram(vertexShaderCode, fragmentShaderCode);

		// 在构造函数中初始化缓冲区
		vertexBuffer = ByteBuffer.allocateDirect(squareCoords.length * 4)
				.order(ByteOrder.nativeOrder())
				.asFloatBuffer()
				.put(squareCoords);
		vertexBuffer.position(0);

		textureBuffer = ByteBuffer.allocateDirect(textureCoords.length * 4)
				.order(ByteOrder.nativeOrder())
				.asFloatBuffer()
				.put(textureCoords);
		textureBuffer.position(0);
	}

	// 创建着色器程序
	private void createProgram(String vertexSource, String fragmentSource) {
		int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
		int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
		mProgram = GLES20.glCreateProgram();
		GLES20.glAttachShader(mProgram, vertexShader);
		GLES20.glAttachShader(mProgram, fragmentShader);
		GLES20.glLinkProgram(mProgram);
		GLES20.glUseProgram(mProgram);
		mPositionHandle = GLES20.glGetAttribLocation(mProgram, "position");
		mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "texCoord");
		mSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
	}

	// 加载着色器
	private int loadShader(int type, String shaderSource) {
		int shader = GLES20.glCreateShader(type);
		GLES20.glShaderSource(shader, shaderSource);
		GLES20.glCompileShader(shader);
		return shader;
	}

	final Object extractLock = new Object();
	int targetPosOfVideo;
	boolean seeking = false;
	//if ignore time check, add extracting to check if notify callback
	//or else, it will notify after player is started.
	boolean extracting = false;
	@SuppressLint("WrongConstant")
	public void extract(int posOfVideoInMs){
		synchronized (extractLock) {
			targetPosOfVideo = posOfVideoInMs;
			seeking = true;
			extracting = true;
			while (!isPrepared) {
				Log.w(TAG, "extract " + posOfVideoInMs + " ms failed: MediaPlayer is not ready.");
				try {
					Thread.sleep(15);
				} catch (InterruptedException ignore) {}
			}

			{
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
					mediaPlayer.seekTo(posOfVideoInMs, MediaPlayer.SEEK_NEXT_SYNC);
				}else{
					mediaPlayer.seekTo(posOfVideoInMs);
				}
			}
			try {
				Log.d(TAG, "extract " + posOfVideoInMs + " ms, and start extractLock wait");
				extractLock.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public void extract(float posRatio){
		while(!isPrepared){
			try {
				sleep(10);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
		int pos = (int)(posRatio * videoDuration);
		extract(pos);
	}
	// 截取指定时间的图像帧
	private void drawFrameLoop() {
		synchronized (drawLock) {
			long pos = mediaPlayer.getCurrentPosition();
			Log.d(TAG, "drawFrameLoop at " + pos  + " ms");
            surfaceTexture.updateTexImage();
			surfaceTexture.getTransformMatrix(mSTMatrix);

			GLES20.glViewport(0, 0, bitmapWidth, bitmapHeight);
			GLES10.glClearColor(color[0], color[1], color[2], 1.0f); // 设置背景颜色为黑色
			GLES10.glClear(GL10.GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区

			GLES20.glUseProgram(mProgram);
			// 传递顶点数据
			vertexBuffer.position(0);
			GLES20.glEnableVertexAttribArray(mPositionHandle);
			GLES20.glVertexAttribPointer(mPositionHandle, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);

			// 传递纹理坐标数据
			textureBuffer.position(0);
			GLES20.glEnableVertexAttribArray(mTexCoordHandle);
			GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);

			// 传递纹理矩阵
			GLES20.glUniformMatrix4fv(mSTMatrixHandle, 1, false, mSTMatrix, 0);
			// 绘制纹理
			GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexBuffer.limit() / 2);
			Bitmap bm = toBitmap();
			if(extracting && !seeking) {
				boolean notifyCallback = ignoreTimeCheck || (targetPosOfVideo > 0 && pos >= targetPosOfVideo);
				if(notifyCallback) {
					targetPosOfVideo = 0;
					if (oel != null) oel.onFrameExtract(this, bm);
					synchronized (extractLock) {
						Log.d(TAG, "drawFrameLoop notify extractLock");
						extractLock.notify();
					}
				}
			}
			try{drawLock.wait();}catch(Exception ignore){}
        }
	}

	public boolean isDone(){
		return mediaPlayer != null && targetPosOfVideo <= mediaPlayer.getCurrentPosition();
	}

	public Bitmap getBitmap(){
		return toBitmap();
	}

	IntBuffer pixelBuffer;
	private Bitmap toBitmap(){
		// 读取帧缓冲区中的像素
		if(pixelBuffer == null){
			pixelBuffer = IntBuffer.allocate(bitmapWidth * bitmapHeight);
		}else {
			pixelBuffer.rewind();
		}

        GLES20.glReadPixels(0, 0, bitmapWidth, bitmapHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
		// 创建Bitmap并将像素数据写入
		Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
        pixelBuffer.position(0);
		bitmap.copyPixelsFromBuffer(pixelBuffer);
        return bitmap;
	}

	// 释放资源
	@SuppressLint("NewApi")
	public void release() {
		if (mediaPlayer != null) {
			mediaPlayer.release();
		}
		mediaPlayer = null;
		synchronized (drawLock){
            try {
                drawLock.wait(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            drawLock.notifyAll();
		}
		if (surface != null) {
			surface.release();
		}
		if (eglSurface != null) {
			EGL14.eglDestroySurface(eglDisplay, eglSurface);
		}
		if (eglContext != null) {
			EGL14.eglDestroyContext(eglDisplay, eglContext);
		}
		if (eglDisplay != null) {
			EGL14.eglTerminate(eglDisplay);
		}
		GLES20.glDeleteTextures(1, new int[]{textureId}, 0);
	}

	float[] color = {0, 0, 0, 1f};
	public void setColor(float r, float g, float b){
		color[0] = r; color[1] = g; color[2] = b;
	}

	OnFrameExtractListener oel;
	public void setOnFrameExtractListener(OnFrameExtractListener l){
		oel = l;
	}

	@Override
	public void onSeekComplete(MediaPlayer mediaPlayer) {
		Log.d(TAG, "onSeekComplete pos=" + mediaPlayer.getCurrentPosition());
		seeking = false;
	}

	public interface OnFrameExtractListener{
		void onFrameExtract(VideoFrameExtractorGL vfe, Bitmap bm);
	}

	public static Bitmap[] extractBitmaps(int[] targetMs, String path, int bmw, int bmh){
		final int len = targetMs.length;
		final Bitmap[] bms = new Bitmap[len];
		VideoFrameExtractorGL vfe = new VideoFrameExtractorGL(path, bmw, bmh, new OnFrameExtractListener() {
			int count = 0;
			@Override
			public void onFrameExtract(VideoFrameExtractorGL vfe, Bitmap bm) {
				Log.d("VFEGL", "extractBitmaps");
				bms[count] = bm;
				count ++;
				if(count >= len){
					vfe.release();
				}
			}
		});
		vfe.start();
		for(int pos : targetMs){
			vfe.extract(pos);
		}

		Log.d("VFEGL", "extractBitmaps done");

		return bms;
	}

	boolean ignoreTimeCheck = false;
	public static Bitmap[] extractBitmaps(String path, int bmw, int bmh,
										  final float durationRatio, final int bitmapCount){
		final Bitmap[] bms = new Bitmap[bitmapCount];
		VideoFrameExtractorGL vfe = new VideoFrameExtractorGL(path, bmw, bmh, new OnFrameExtractListener() {
			int count = 0;
			@Override
			public void onFrameExtract(VideoFrameExtractorGL vfe, Bitmap bm) {
				Log.d("VFEGL", "extractBitmaps");
				bms[count] = bm;
				count ++;
				if(count >= bitmapCount){
					vfe.release();
				}
			}
		});
		vfe.start();
		vfe.ignoreTimeCheck = true;
		vfe.extract(durationRatio);

		Log.d("VFEGL", "extractBitmaps done");

		return bms;
	}
}

基本的流程如下:

  1. 初始化MeidaPlayer 用与播放视频
  2. 初始化OpenGL环境, 绑定Texture 和 SurfaceTexture
  3. 使用SurfaceTexutre创建Surface, 并为MediaPlayer 设置Surface, 这样视频就会绘制到Surface上
  4. 通过SurfaceTexture的setOnFrameAvailableListener回调绘制帧数据
  5. 从OpenGL中提取出glReadPixels提取出像素数据, 填充到Bitmap

调用

		Bitmap[] bms = VideoFrameExtractorGL.extractBitmaps(path, 128, 72, 0.5f, bmCount);

需注意:

  1. 注意OpenGLES 的版本, 1.x 不支持离屏渲染, 2.x 需要配合着色器渲染图像
  2. 构建OpenGL的环境和渲染的工作, 要放在同一个线程中.

参考

Kotlin拿Android本地视频缩略图
Android视频图片缩略图的获取
Android 获取视频缩略图(获取视频每帧数据)的优化方案
37.4 OpenGL离屏渲染环境配置
ExtractMpegFramesTest.java

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

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

相关文章

数字化转型背景下,高职院校计算机网络应用的革新策略

在当今信息化时代&#xff0c;计算机网络已经成为高职院校教育不可或缺的一部分&#xff0c;它不仅极大地丰富了教育资源&#xff0c;提高了交流的便捷性&#xff0c;还催生了多样化的教学模式。对于高职院校来说&#xff0c;加强计算机网络应用的建设不仅是顺应时代潮流的必然…

【K230 CanMV】图像识别-摄像头获取图像 Sensor 函数全解析

引言&#xff1a;随着图像处理技术的不断发展&#xff0c;摄像头在嵌入式系统中的应用越来越广泛&#xff0c;尤其是在智能监控、自动驾驶、机器人视觉等领域。K230作为一款高性能的嵌入式处理器&#xff0c;提供了强大的图像处理能力&#xff0c;支持多种类型的摄像头接入与图…

SQL优化与性能——数据库设计优化

数据库设计优化是提高数据库性能、确保数据一致性和支持业务增长的关键环节。无论是大型企业应用还是小型项目&#xff0c;合理的数据库设计都能够显著提升系统性能、减少冗余数据、优化查询响应时间&#xff0c;并降低维护成本。本章将深入探讨数据库设计中的几个关键技术要点…

用Python做数据分析环境搭建及工具使用(Jupyter)

目录 一、Anaconda下载、安装 二、Jupyter 打开 三、Jupyter 常用快捷键 3.1 创建控制台 3.2 命令行模式下的快捷键 3.3 运行模式下快捷键 3.4 代码模式和笔记模式 3.5 编写Python代码 一、Anaconda下载、安装 【最新最全】Anaconda安装python环境_anaconda配置python…

Jmeter测试工具的安装和使用,mac版本,jmeter版本5.2.1

Jmeter测试工具的安装和使用JSON格式请求 一、安装1、安装jdk包和设置java环境2、去官网下载Jmeter3、解压后&#xff0c;打开mac终端&#xff0c;进入apache-jmeter的bin文件开启jmeter 二、使用jmeter1、添加线程2、添加HTTP请求3、配置请求的协议、IP地址、端口号、请求方法…

基于springboot 的体质测试数据分析及可视化设计LWPPT

技术可行性&#xff1a;技术背景 本企业网站在Windows操作系统中进行开发&#xff0c;并且目前PC机的性能已经可以胜任普通网站的web服务器。系统开发所使用的技术也都是自身所具有的&#xff0c;也是当下广泛应用的技术之一。 系统的开发环境和配置都是可以自行安装的&#x…

【初阶数据结构和算法】二叉树顺序结构---堆的定义与实现(附源码)

文章目录 一、堆的定义与结构二、堆的实现1.堆的初始化和销毁堆的初始化堆的销毁 2.向上调整算法和入堆向上调整算法入堆 3.向下调整算法和出堆顶数据向下调整算法出堆 4.堆的有效数据个数和判空堆的有效数据个数堆的判空 5.取堆顶数据 三、堆的源码 一、堆的定义与结构 本篇内…

黑马程序员Java笔记整理(day05)

1.面向对象编程 2.用法 3.对象是什么 4.对象在计算机中是啥 5.无参与有参构造器 小结: 6.this的作用 7.小结 8.封装 9.小结 10.实体类 11.小结 12.static 13.小结 14.static修饰方法 15.static应用前景 16.几个注意事项 17.java中可以直接用类的名字创建数组&#xff0c;如: M…

管理锻炼数据_创建类

● 这篇文章和大家一起学习创建类来管理我们的锻炼数据 ● 首先我们先创建这些类&#xff0c;然后讲锻炼数据中的数据写出来 class Workout {date new Date();constructor(coords, distance, duration) {this.coords coords;this.distance distance; //kmthis.duration du…

241127学习日志——[CSDIY] [InternStudio] 大模型训练营 [20]

CSDIY&#xff1a;这是一个非科班学生的努力之路&#xff0c;从今天开始这个系列会长期更新&#xff0c;&#xff08;最好做到日更&#xff09;&#xff0c;我会慢慢把自己目前对CS的努力逐一上传&#xff0c;帮助那些和我一样有着梦想的玩家取得胜利&#xff01;&#xff01;&…

qt QAnimationDriver详解

1、概述 QAnimationDriver是Qt框架中提供的一个类&#xff0c;它主要用于自定义动画帧的时间控制和更新。通过继承和实现QAnimationDriver&#xff0c;开发者可以精确控制动画的时间步长和更新逻辑&#xff0c;从而实现丰富和灵活的动画效果。QAnimationDriver与QAbstractAnim…

更多开源创新 挑战OpenAI-o1的模型出现和AI个体模拟突破

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

CSP-J初赛不会备考咋办?

以下备考攻略仅供参考&#xff0c;如需资料请私信作者&#xff01;求支持&#xff01; 目录 一、编程语言基础 1.语法知识 -变量与数据类型 -运算符 -控制结构 -函数 2.标准库的使用 -输入输出流 -字符串处理 -容器类&#xff08;可选&#xff09; 二、算法与数据结构 1.基…

电路基础——相量法

相量法 为什么要使用相量表示&#xff1f; 电路方程是微分方程&#xff1a; 电路的运算&#xff08;如KCL、KVL方程运算&#xff09;会涉及到两个正弦量的相加&#xff1a; 如下图所示同频率的正弦量相加仍得到同频率的正弦量&#xff0c;因此只需确定初相位和有效值。 基于上…

第七课 Unity编辑器创建的资源优化_UI篇(UGUI)

上期我们学习了简单的Scene优化&#xff0c;接下来我们继续编辑器创建资源的UGUI优化 UI篇&#xff08;UGUI&#xff09; 优化UGUI应从哪些方面入手&#xff1f; 可以从CPU和GPU两方面考虑&#xff0c;CPU方面&#xff0c;避免触发或减少Canvas的Rebuild和Rebatch&#xff0c…

如何使用ST7789展现图片?[ESP--4]

本节我们继续ESP和ST 7789的话题&#xff0c;这节课我们来学学如何展示图片,话不多说&#xff0c;先上效果 好&#xff0c;教程开始~前情提要&#xff0c;要看懂这篇&#xff0c;建议搭配楼主的前两期文章 使用ESP32驱动LCD-ST7789屏幕[ESP–2] 加速你的LCD-ST7789屏幕&#xf…

代码随想录day02--链表

移除链表元素 题目 地址&#xff1a;https://leetcode.cn/problems/remove-linked-list-elements/description/ 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 思路是使用虚拟节点的…

electron-updater软件自动检测更新 +无服务器本地测试

大家好&#xff0c;我是小黄。 今天分享一下如何0基础实现electron自动检测更新功能。 一. 安装 electron-updater 实现自动更新 安装依赖 electron-updater npm install electron-updater 二. 修改package.josn "publish": {"provider": "generi…

【Linux——实现一个简易shell】

黑暗中的我们都没有说话&#xff0c;你只想回家&#xff0c;不想你回家............................................................... 文章目录 前言 一、【shell工作过程】 二、【命令行参数】 2.1、【获取命令行参数】 1、【输出命令行提示符】 2、【输入命令行参数】 2…

【超全总结】深度学习分割模型的损失函数类别及应用场景

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…