Android OpenGLES2.0开发(十):FBO离屏渲染

人生是一场单程的旅行,即使有些遗憾我们也没有从头再来的机会,与其纠结无法改变的过去不如微笑着珍惜未来。

  • Android OpenGLES开发:EGL环境搭建
  • Android OpenGLES2.0开发(一):艰难的开始
  • Android OpenGLES2.0开发(二):环境搭建
  • Android OpenGLES2.0开发(三):绘制一个三角形
  • Android OpenGLES2.0开发(四):矩阵变换和相机投影
  • Android OpenGLES2.0开发(五):绘制正方形和圆形
  • Android OpenGLES2.0开发(六):着色器语言GLSL
  • Android OpenGLES2.0开发(七):纹理贴图之显示图片
  • Android OpenGLES2.0开发(八):Camera预览
  • Android OpenGLES2.0开发(九):图片滤镜
  • Android OpenGLES2.0开发(十):FBO离屏渲染

引言

之前的章节我们使用OpenGL ES绘制图形然后显示到屏幕上,貌似看着并没有什么问题。

如果我们现在不想显示到屏幕中,但还想利用OpenGL ES对图像的强大处理能力那又该如何是好?抑或我们上一节学习到的图片滤镜,真实的情况需要多个滤镜叠加使用,把所有的滤镜都放到一个着色器中似乎不是一种优秀的编程模式。

上面提到问题,接下来我们会给出答案。

什么是FBO

FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO)。

FBO可以让我们的渲染不渲染到屏幕上,而是渲染到离屏Buffer中。可以理解为将OpenGL ES绘制的图像内容输出到一块内存中,而我们可以对这块内存进行多次绘制。当然这块内存中的数据也可保存到本地SD卡中,也可重新绘制到屏幕上用于显示。

  • FBO是一个容器,自身不能用于渲染,需要与一些可渲染的缓冲区绑定在一起,像纹理或者渲染缓冲区。
  • 可以给他添加添加纹理(Texture )或渲染缓冲区对象(RBO)
  • 它仅且提供了 3 个附着(Attachment):颜色附着深度附着模板附着
    请添加图片描述

FBO优点

  1. 灵活性和控制性‌:FBO允许开发者将渲染结果输出到自定义的帧缓冲区,而不是传统的窗口系统提供的帧缓冲区。这使得开发者可以更灵活地控制渲染过程和结果,适用于需要特殊渲染效果的场景‌
  2. 性能优化‌:通过使用FBO,开发者可以避免不必要的渲染操作,例如在不需要渲染到屏幕的情况下,可以直接将渲染结果存储在内存中,从而提高渲染效率‌
  3. 独立于窗口系统‌:FBO完全受OpenGL控制,不依赖于窗口系统提供的帧缓存。这意味着它可以在没有窗口系统的情况下使用,适用于无窗口环境的渲染需求‌
  4. 多目标应用‌:FBO可以用于多种应用场景,包括但不限于实时渲染、图像处理、视频游戏开发等。其灵活性和高性能使其在需要高质量图形输出的应用中具有显著优势‌

FBO怎么用

我们对OpenGL ES的绘制流程很熟悉了,如何将渲染结果输出到FBO中其实很简单。

  1. 创建FBO缓冲区,在这期间需要绑定纹理和深度缓冲区(可选)
  2. 渲染到FBO,与之前的渲染没有本质的区别,只是在draw前绑定FBO缓冲区,绘制结束解绑FBO即可
  3. 释放FBO,将FBO缓冲区释放

1. 创建FBO

创建FBO基本步骤是:

  • 创建2D纹理(颜色缓冲区)
  • 创建帧缓冲区
  • 创建深度缓冲区
  • 将纹理和深度缓冲区附着到帧缓冲区上
/*** 创建帧缓冲区(FBO)** @param width* @param height*/
public void createFrameBuffers(int width, int height) {if (mFrameBuffer > 0) {destroyFrameBuffers();}// 1.创建一个纹理对象并绑定它,这将是颜色缓冲区。int[] values = new int[1];GLES20.glGenTextures(1, values, 0);GLESUtils.checkGlError("glGenTextures");mOffscreenTexture = values[0];GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);GLESUtils.checkGlError("glBindTexture " + mOffscreenTexture);// 2.创建纹理存储对象GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);// 3.设置参数。我们可能正在使用二维的非幂函数,所以某些值可能无法使用。// 设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);//设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);//设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);GLESUtils.checkGlError("glTexParameter");// 4.创建帧缓冲区对象并将其绑定GLES20.glGenFramebuffers(1, values, 0);GLESUtils.checkGlError("glGenFramebuffers");mFrameBuffer = values[0];    // expected > 0GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);GLESUtils.checkGlError("glBindFramebuffer " + mFrameBuffer);// 5.创建深度缓冲区并绑定它GLES20.glGenRenderbuffers(1, values, 0);GLESUtils.checkGlError("glGenRenderbuffers");mDepthBuffer = values[0];    // expected > 0GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);GLESUtils.checkGlError("glBindRenderbuffer " + mDepthBuffer);// 为深度缓冲区分配存储空间。GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);GLESUtils.checkGlError("glRenderbufferStorage");// 6.将深度缓冲区和纹理(颜色缓冲区)附着到帧缓冲区对象GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,GLES20.GL_RENDERBUFFER, mDepthBuffer);GLESUtils.checkGlError("glFramebufferRenderbuffer");GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);GLESUtils.checkGlError("glFramebufferTexture2D");// 检查是否一切正常int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {throw new RuntimeException("Framebuffer not complete, status=" + status);}// 解绑纹理GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);// 解绑Frame BufferGLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);// 解绑Render BufferGLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);GLESUtils.checkGlError("prepareFramebuffer done");
}

2. 渲染到FBO

渲染其实很简单,onDraw还是执行原本的绘制流程,只是在方法前和后添加了FBO的逻辑

public int draw(int textureId, float[] matrix) {GLES20.glViewport(0, 0, mWidth, mHeight);if (bindFBO) {// 绑定FBOGLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);}onDraw(textureId, matrix);if (bindFBO) {// 解绑FBOGLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);//返回fbo的纹理idreturn mOffscreenTexture;} else {return textureId;}
}

3. 释放FBO

/*** 销毁帧缓冲区(FBO)*/
public void destroyFrameBuffers() {// 删除fbo的纹理if (mOffscreenTexture > 0) {GLES20.glDeleteTextures(1, new int[]{mOffscreenTexture}, 0);mOffscreenTexture = -1;}if (mFrameBuffer > 0) {GLES20.glDeleteFramebuffers(1, new int[]{mFrameBuffer}, 0);mFrameBuffer = -1;}if (mDepthBuffer > 0) {GLES20.glDeleteRenderbuffers(1, new int[]{mDepthBuffer}, 0);mDepthBuffer = -1;}
}

完整代码

在上一篇中我们实现了多个图片滤镜的效果,为了使得他们都支持FBO功能,我们建立一个抽象类将FBO的功能放到抽象类中,完整的代码如下:

先定义个接口如下:

public interface AFilter {void setTextureSize(int width, int height);void setFrameBuffer(int frameBuffer);int getFrameBuffer();int getOffscreenTexture();void setBindFBO(boolean bindFBO);void bindFBO();void unBindFBO();void surfaceCreated();void surfaceChanged(int width, int height);int draw(int textureId, float[] matrix);void onDraw(int textureId, float[] matrix);void release();
}

抽象类中实现FBO功能:

/*** 滤镜效果抽象类*/
public abstract class BaseFilter implements AFilter {/*** 离屏渲染纹理id*/private int mOffscreenTexture = -1;/*** 帧缓冲区*/private int mFrameBuffer = -1;/*** 深度缓冲区*/private int mDepthBuffer = -1;/*** 是否使用离屏渲染*/protected boolean bindFBO;private int mWidth;private int mHeight;@Overridepublic void setBindFBO(boolean bindFBO) {this.bindFBO = bindFBO;}@Overridepublic void setFrameBuffer(int frameBuffer) {mFrameBuffer = frameBuffer;}@Overridepublic int getFrameBuffer() {return mFrameBuffer;}@Overridepublic int getOffscreenTexture() {return mOffscreenTexture;}@Overridepublic void bindFBO() {if (mFrameBuffer > 0) {GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);}}@Overridepublic void unBindFBO() {GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);}/*** 创建帧缓冲区(FBO)** @param width* @param height*/public void createFrameBuffers(int width, int height) {if (mFrameBuffer > 0) {destroyFrameBuffers();}// 1.创建一个纹理对象并绑定它,这将是颜色缓冲区。int[] values = new int[1];GLES20.glGenTextures(1, values, 0);GLESUtils.checkGlError("glGenTextures");mOffscreenTexture = values[0];GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);GLESUtils.checkGlError("glBindTexture " + mOffscreenTexture);// 2.创建纹理存储对象GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);// 3.设置参数。我们可能正在使用二维的非幂函数,所以某些值可能无法使用。// 设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);//设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);//设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);GLESUtils.checkGlError("glTexParameter");// 4.创建帧缓冲区对象并将其绑定GLES20.glGenFramebuffers(1, values, 0);GLESUtils.checkGlError("glGenFramebuffers");mFrameBuffer = values[0];    // expected > 0GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);GLESUtils.checkGlError("glBindFramebuffer " + mFrameBuffer);// 5.创建深度缓冲区并绑定它GLES20.glGenRenderbuffers(1, values, 0);GLESUtils.checkGlError("glGenRenderbuffers");mDepthBuffer = values[0];    // expected > 0GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);GLESUtils.checkGlError("glBindRenderbuffer " + mDepthBuffer);// 为深度缓冲区分配存储空间。GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);GLESUtils.checkGlError("glRenderbufferStorage");// 6.将深度缓冲区和纹理(颜色缓冲区)附着到帧缓冲区对象GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,GLES20.GL_RENDERBUFFER, mDepthBuffer);GLESUtils.checkGlError("glFramebufferRenderbuffer");GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);GLESUtils.checkGlError("glFramebufferTexture2D");// 检查是否一切正常int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {throw new RuntimeException("Framebuffer not complete, status=" + status);}// 解绑纹理GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);// 解绑Frame BufferGLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);// 解绑Render BufferGLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);GLESUtils.checkGlError("prepareFramebuffer done");}/*** 销毁帧缓冲区(FBO)*/public void destroyFrameBuffers() {// 删除fbo的纹理if (mOffscreenTexture > 0) {GLES20.glDeleteTextures(1, new int[]{mOffscreenTexture}, 0);mOffscreenTexture = -1;}if (mFrameBuffer > 0) {GLES20.glDeleteFramebuffers(1, new int[]{mFrameBuffer}, 0);mFrameBuffer = -1;}if (mDepthBuffer > 0) {GLES20.glDeleteRenderbuffers(1, new int[]{mDepthBuffer}, 0);mDepthBuffer = -1;}}@Overridepublic void surfaceChanged(int width, int height) {mWidth = width;mHeight = height;if (bindFBO) {createFrameBuffers(width, height);}}public int draw(int textureId, float[] matrix) {GLES20.glViewport(0, 0, mWidth, mHeight);if (bindFBO) {// 绑定FBObindFBO();}onDraw(textureId, matrix);if (bindFBO) {// 解绑FBOunBindFBO();//返回fbo的纹理idreturn mOffscreenTexture;} else {return textureId;}}@Overridepublic void release() {destroyFrameBuffers();}
}

FBO示例

1. 灰度图渲染并将结果保存到SD卡

我们想从显存中获取图像数据,OpenGL ES为我们提供了一个接口GLES20.glReadPixels,在图像渲染完后,我们可以通过该API获取图像数据到内存中

public static native void glReadPixels(int x,int y,int width,int height,int format,int type,java.nio.Buffer pixels
);

这个接口可以获取RGBA原始图像数据,我们可以将他转换为Bitmap,然后Bitmap转为byte[]保存到本地

public static byte[] Bitmap2Bytes(Bitmap bm, int compress) {ByteArrayOutputStream baos = new ByteArrayOutputStream();bm.compress(Bitmap.CompressFormat.JPEG, compress, baos);return baos.toByteArray();
}private Bitmap readBufferPixelToBitmap(int width, int height) {ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);buf.order(ByteOrder.LITTLE_ENDIAN);GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);buf.rewind();Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);bmp.copyPixelsFromBuffer(buf);return bmp;
}

完整的渲染代码如下:

static class MyRenderer implements Renderer {private Context mContext;private GrayFilter mImageFilter = new GrayFilter();private int mTextureId;private Bitmap mBitmap;private float[] mMVPMatrix = new float[16];public MyRenderer(Context context) {mContext = context;mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.girl);mImageFilter.setBindFBO(true);Matrix.setIdentityM(mMVPMatrix, 0);}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {Log.i(TAG, "onSurfaceCreated...");mTextureId = GLESUtils.create2DTexture(mBitmap);mImageFilter.surfaceCreated();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {Log.i(TAG, "onSurfaceChanged...");mImageFilter.surfaceChanged(mBitmap.getWidth(), mBitmap.getHeight());}@Overridepublic void onDrawFrame(GL10 gl) {long start = System.currentTimeMillis();mImageFilter.draw(mTextureId, mMVPMatrix);Log.i(TAG, "onDrawFrame:" + (System.currentTimeMillis() - start) + "ms");mImageFilter.bindFBO();Bitmap resultBitmap = readBufferPixelToBitmap(mBitmap.getWidth(), mBitmap.getHeight());mImageFilter.unBindFBO();Log.i(TAG, "readBufferPixelToBitmap:" + (System.currentTimeMillis() - start) + "ms");byte[] jpgData = Bitmap2Bytes(resultBitmap, 100);FileUtils.writeFile(mContext.getExternalFilesDir("gles").getAbsolutePath() + File.separator + "gray.jpg", jpgData);}@Overrideprotected void finalize() throws Throwable {super.finalize();if (mImageFilter != null) {mImageFilter.release();}}public static byte[] Bitmap2Bytes(Bitmap bm, int compress) {ByteArrayOutputStream baos = new ByteArrayOutputStream();bm.compress(Bitmap.CompressFormat.JPEG, compress, baos);return baos.toByteArray();}private Bitmap readBufferPixelToBitmap(int width, int height) {ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);buf.order(ByteOrder.LITTLE_ENDIAN);GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);buf.rewind();Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);bmp.copyPixelsFromBuffer(buf);return bmp;}
}

注意:我们在调用glReadPixels前需要绑定FBO否则无法读取到图像,读取完成后解绑FBO

脱离了屏幕渲染,我们利用GPU强大的图像处理能力,通过后台计算导出了图像数据,该灰度图是从SD卡中获取
请添加图片描述

2. 多种滤镜叠加

如果没有FBO,我们要实现多种滤镜叠加,我们只能把所有的滤镜都写到一个GLSL中,通过流程控制来使用哪些滤镜。而随着代码量的增加,滤镜的增加删除都需要改GLSL这无疑是灾难。

而现在有了FBO,我们可以动态的对这块显存操作,使用不同的滤镜渲染。这样我们可以像链式调用一样,动态的添加删除滤镜。
在这里插入图片描述

我们使用了灰度滤镜,亮度滤镜以及将FBO绘制到屏幕上的滤镜

  1. 首先我们将原图绘制到灰度滤镜的FBO中
  2. 我们将灰度FBO对应纹理作为下一个滤镜的原图传入,将灰度图通过亮度滤镜提亮或变暗
  3. 最后我们将亮度FBO对应纹理进行显示,实现屏幕显示多种滤镜叠加的效果
private GrayFilter mGrayFilter;
private HueFilter mHueFilter;
private OriginFilter mOriginFilter;@Override
public void onDraw(int textureId, float[] matrix) {int fboId = mGrayFilter.draw(textureId, MatrixUtils.getOriginalMatrix());fboId = mHueFilter.draw(fboId, MatrixUtils.getOriginalMatrix());mOriginFilter.draw(fboId, matrix);
}

这里需要注意下:我们GrayFilterHueFilter内部都拥有自己的FBO。看起来没有问题,如果现在要叠加几十个滤镜,每个滤镜都有自己的FBO,显存会吃不消。
聪明的你应该能想到,只需要存在2个FBO即可实现不限数量的滤镜叠加,一个FBO用于存储上一次渲染的结果,一个FBO存储当前渲染的结果,两个FBO做到正确切换即可。

多滤镜叠加代码如下:

/*** 离屏渲染多种滤镜叠加*/
public class OverlayFilter extends BaseFilter {private int mTextureWidth;private int mTextureHeight;private GrayFilter mGrayFilter;private HueFilter mHueFilter;private OriginFilter mOriginFilter;public OverlayFilter() {mGrayFilter = new GrayFilter();mGrayFilter.setBindFBO(true);mHueFilter = new HueFilter(new float[]{-0.2f, -0.2f, -0.2f});mHueFilter.setBindFBO(true);mOriginFilter = new OriginFilter();}@Overridepublic void setTextureSize(int width, int height) {mTextureWidth = width;mTextureHeight = height;mGrayFilter.setTextureSize(mTextureWidth, mTextureHeight);mHueFilter.setTextureSize(mTextureWidth, mTextureHeight);}@Overridepublic void surfaceCreated() {mGrayFilter.surfaceCreated();mHueFilter.surfaceCreated();mOriginFilter.surfaceCreated();}@Overridepublic void surfaceChanged(int width, int height) {super.surfaceChanged(width, height);mGrayFilter.surfaceChanged(mTextureWidth, mTextureHeight);mHueFilter.surfaceChanged(mTextureWidth, mTextureHeight);mOriginFilter.surfaceChanged(width, height);}@Overridepublic void onDraw(int textureId, float[] matrix) {int fboId = mGrayFilter.draw(textureId, MatrixUtils.getOriginalMatrix());fboId = mHueFilter.draw(fboId, MatrixUtils.getOriginalMatrix());mOriginFilter.draw(fboId, matrix);}@Overridepublic void release() {super.release();mGrayFilter.release();mHueFilter.release();mOriginFilter.release();}
}

灰度图+变暗两种效果叠加效果图灰度图+变暗两种效果叠加效果图

最后

该章节我们学习了OpenGL ES的一种高端技术离屏渲染,我们可以不依赖屏幕就能做到对图像进行后台渲染,他比直接渲染到屏幕更加高效,也为我们深入使用OpenGL ES提供了新思路,新方法。

OpenGL ES系列:https://github.com/xiaozhi003/AndroidOpenGLDemo.git,如果对你有帮助可以star下,万分感谢^_^

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

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

相关文章

Magnet: 基于推送的大规模数据处理Shuffle服务

本文翻译自:《Magnet: Push-based Shuffle Service for Large-scale Data Processing》 摘要 在过去的十年中,Apache Spark 已成为大规模数据处理的流行计算引擎。与其他基于 MapReduce 计算范式的计算引擎一样,随机Shuffle操作(即…

Pytorch | 利用VA-I-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用VA-I-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集VA-I-FGSM介绍相关定义算法流程 VAI-FGSM代码实现VAI-FGSM算法实现攻击效果 代码汇总vaifgsm.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器: Pytorch | 从零构建AlexNet对…

WebPack3项目升级webpack5的配置调试记录

文章目录 前言一、webpack3环境1.1、知识点记录1.1.1、配置解释1.1.2、webpack与sass版本对应关系1.1.3、CommonJS与ESModule1.1.4、node版本管理nvm1.1.5、sass-loader、sass与node-sass 1.2、其他1.2.1、.d.ts是什么文件1.2.2、react与types/react版本对应关系1.2.3、webpack…

MySQL 数据”丢失”事件之 binlog 解析应用

事件背景 客户反馈在晚间数据跑批后,查询相关表的数据时,发现该表的部分数据在数据库中不存在 从应用跑批的日志来看,跑批未报错,且可查到日志中明确显示当时那批数据已插入到数据库中 需要帮忙分析这批数据丢失的原因。 备注:考虑信息敏感性,以下分析场景测试环境模拟,相关数据…

微信小程序的轮播图学习报告

微信小程序轮播图学习报告 好久都没分享新内容了,实在惭愧惭愧。今天给大家做一个小程序轮播图的学习报告。 先给大家看一下我的项目状态: 很空昂!像一个正在修行的老道,空的什么也没有。 但是我写了 4 个 view 容器,…

【RAII | 设计模式】C++智能指针,内存管理与设计模式

前言 nav2系列教材,yolov11部署,系统迁移教程我会放到年后一起更新,最近年末手头事情多,还请大家多多谅解。 上一节我们讲述了C移动语义相关的知识,本期我们来看看C中常用的几种智能指针,并看看他们在设计模式中的运…

微软 CEO 萨提亚・纳德拉:回顾过去十年,展望 AI 时代的战略布局

近日,微软 CEO 萨提亚・纳德拉与著名投资人比尔・格里和布拉德・格斯特纳进行了一场深度对话,回顾了过去十年微软的转型历程,并展望了 AI 时代的战略布局。在这次访谈中,纳德拉分享了他在微软的早期经历,包括他加入微软…

【Java-tesseract】OCR图片文本识别

文章目录 一、需求二、概述三、部署安装四、技术细节五、总结 一、需求 场景需求:是对识别常见的PNG,JPEG,TIFF,GIF图片识别,环境为离线内网。组件要求开源免费,并且可以集成Java生成接口服务。 二、概述 我不做选型对比了,我筛选测试了下Tesseract(v…

iOS开发代码块-OC版

iOS开发代码块-OC版 资源分享资源使用详情Xcode自带代码块自定义代码块 资源分享 自提: 通过网盘分享的文件:CodeSnippets 2.zip 链接: https://pan.baidu.com/s/1Yh8q9PbyeNpuYpasG4IiVg?pwddn1i 提取码: dn1i Xcode中的代码片段默认放在下面的目录中…

如何借助边缘智能网关实现厂区粉尘智能监测告警

在诸如木制品加工、纺织品加工、塑料橡胶制品加工等多种工业生产场景中,粉尘问题的隐患和风险不可小觑。如果缺少对生产环境中粉尘的监测和管理,可能发生易燃易爆、环境污染和工人尘肺等生产事故。 针对工业场景中的粉尘状况监测、管理及预警&#xff0c…

McDonald‘s Event-Driven Architecture 麦当劳事件驱动架构

原文链接 1 mcdonalds-technical-blog/ 原文链接 2 mcdonalds-technical-blog/ 麦当劳在异步、事务性和分析性处理用例中使用跨技术栈的事件,包括移动订单进度跟踪和向客户发送营销通信(交易和促销)。 统一事件平台(unified eve…

EasyExcel停更,FastExcel接力

11月6日消息,阿里巴巴旗下的Java Excel工具库EasyExcel近日宣布,将停止更新,未来将逐步进入维护模式,将继续修复Bug,但不再主动新增功能。 EasyExcel以其快速、简洁和解决大文件内存溢出的能力而著称,官方…

HarmonyOS NEXT 实战之元服务:静态多案例效果(一)

背景: 前几篇学习了元服务,后面几期就让我们开发简单的元服务吧,里面丰富的内容大家自己加,本期案例 仅供参考 先上本期效果图 ,里面图片自行替换 效果图1代码案例如下: import { authentication } from…

前端(Ajax)

1.客户端请求 向https://jsonplaceholder.typicode.com/users发送get请求 const xhr new XMLHttpRequest(); console.log(xhr.readyState); xhr.open(‘get’, ‘https://jsonplaceholder.typicode.com/users’) console.log(xhr.readyState); xhr.send(); console.log(xhr.…

java高频面试之SE-05

面试官:java中为什么有多态? 面试官你好!Java 中有多态主要是为了实现灵活性和可扩展性。通过多态,可以用统一的接口处理不同的对象,从而提高代码的可维护性和可复用性。以下是多态的几个关键原因: 1. 代…

DP83848以太网移植流程,可以TCP通信

DP83848-EP 是一款高度可靠、功能丰富的强大器件,包含了增强型 ESD 保护、MII 和 RMII,从而在 MPU 选择方面实现最大的灵活性,所有这些特性都融入于 48 引脚 PQFP 封装中。 DP83848-EP 配备 集成子层以支持 10BASE-T 和 100BASE-TX 以太网协议,这些协议确保了与基于其他标…

波动理论、传输线和S参数网络

波动理论、传输线和S参数网络 传输线 求解传输线方程 对于传输线模型,我们通常用 R L G C RLGC RLGC 来表示: 其中 R R R 可以表示导体损耗,由于电子流经非理想导体而产生的能量损耗。 G G G 表示介质损耗,由于非理想电介质…

基于pytorch的深度学习基础3——模型创建与nn.Module

三 模型创建与nn.Module 3.1 nn.Module 模型构建两要素: 构建子模块——__init()__拼接子模块——forward() 一个module可以有多个module; 一个module相当于一个运算,都必须实现forward函数; 每一个mod…

Android--java实现手机亮度控制

文章目录 1、开发需求2、运行环境3、主要文件4、布局文件信息5、手机界面控制代码6、debug 1、开发需求 需求:开发一个Android apk实现手机亮度控制 2、运行环境 Android studio最新版本 3、主要文件 app\src\main\AndroidManifest.xml app\src\main\res\layou…

Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的

文章目录 一、前言二、主要内容三、小结 🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中,数组索引从 1 开始是很常见的。例如,Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…