Camera Api 2 和 OPEN GL ES 使用(顯示濾鏡效果)

Camera Api 2 和 OPEN GL ES 使用(顯示濾鏡效果)

相機預覽和open GL 使用實現濾鏡效果
代碼 https://github.com/loggerBill/camera

在這里插入圖片描述

相機預覽

1.相機動態權限

    <uses-permission android:name="android.permission.CAMERA" /><uses-featureandroid:name="android.hardware.camera"android:required="true" />
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);}

2.打開相機

private void openCamera() {
...manager.openCamera(cameraId, new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {cameraDevice = camera;createCameraPreviewSession();}
...          
}

3.創建session和CaptureRequest

    private void createCameraPreviewSession() {
...try {final CaptureRequest.Builder previewRequestBuilder =cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);previewRequestBuilder.addTarget(previewSurface);cameraDevice.createCaptureSession(Arrays.asList(previewSurface),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {captureSession = session;try {// 設置重復請求captureSession.setRepeatingRequest(previewRequestBuilder.build(),null, null);} catch (CameraAccessException e) {e.printStackTrace();}}...}

創建需要告訴相機有那些可以輸出的surface,和CaptureRequest 中addTarget surface.
surface 中會帶有size。
要是正常使用TextureView 顯示預覽代碼:

public class CameraPreview extends TextureView implements TextureView.SurfaceTextureListener {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {openCamera(width, height);}private void openCamera(int width, int height) {
...texture.setDefaultBufferSize(largest.getWidth(), largest.getHeight());Surface surface = new Surface(texture);previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);previewRequestBuilder.addTarget(surface);
...}

這樣創建surface 并且與Request綁定等待預覽上來就會顯示到TextureView中。
但我們相實現濾鏡效果,TextureView 做不到修改效果。
所以我們用到了GLSurfaceView。

OPENGL

GLSurfaceView

1.創建自定義view 繼承自GLSurfaceView。

public class CameraGLSurfaceView extends GLSurfaceView {private final CameraGLRenderer renderer;public CameraGLSurfaceView(Context context, AttributeSet attrs) {super(context, attrs);setEGLContextClientVersion(2); // 使用 OpenGL ES 2.0renderer = new CameraGLRenderer(context,this);setRenderer(renderer);setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 有幀時渲染,需要手動調用requestRender}public Surface getSurface() {return renderer.getSurface();}public void setPreviewSize(Size size) {renderer.setPreviewSize(size);}public CameraGLRenderer getRenderer(){return renderer;}
}

setEGLContextClientVersion(2); // 使用 OpenGL ES 2.0

  • OpenGL ES 2.0 支持可編程渲染管線(使用 GLSL 著色器),而 1.x 是固定管線。
  • 2.0 版本提供更靈活的圖形控制(如自定義濾鏡、特效等),適合相機圖像處理。

創建自定義渲染器

  • 作用:實例化自定義渲染器 CameraGLRenderer
  • 參數
    • context:傳遞上下文給渲染器(可能用于資源加載)。
    • this:將當前 GLSurfaceView 實例傳給渲染器(便于渲染器與視圖交互)。
  • 渲染器職責
    • 實現 GLSurfaceView.Renderer 接口。
    • 重寫 onSurfaceCreated(), onSurfaceChanged(), onDrawFrame() 方法。
    • 處理 OpenGL 初始化、相機幀繪制等邏輯。

setRenderer(renderer);

  • 作用:將自定義渲染器綁定到 GLSurfaceView
  • 觸發行為
    • 系統自動創建 OpenGL ES 上下文和渲染線程。
    • 首次調用渲染器的 onSurfaceCreated()onSurfaceChanged()
    • 啟動渲染循環(根據渲染模式觸發 onDrawFrame())。

setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 有幀時渲染

  • 作用:設置為 按需渲染 模式(僅在主動請求時重繪)。
  • 對比默認模式
    • 默認模式 RENDERMODE_CONTINUOUSLY:連續渲染(60 FPS),浪費資源。
    • WHEN_DIRTY 模式:僅在調用 requestRender() 時渲染。
  • 適用場景
    • 相機預覽:當新幀到達時手動調用 requestRender()
    • 節省 CPU/GPU 資源,避免無效渲染。
自定義渲染器–>CameraGLRenderer

實現自定義渲染器需要繼承GLSurfaceView.Renderer。

public class CameraGLRenderer implements GLSurfaceView.Renderer {@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {//首次回調 onSurfaceCreated()// 此處執行一次性初始化操作// 例如:設置清屏顏色、編譯著色器、創建紋理等}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {// 響應視圖尺寸變化gl.glViewport(0, 0, width, height);  // 必須設置視口// 可在此處更新投影矩陣等}@Overridepublic void onDrawFrame(GL10 gl) {// 每幀重復執行}
}

大致明白渲染器的功能,下面我們來實現預覽功能。

1.創建紋理對象。

OpenGL ES 2.0 中生成紋理對象 的標準操作

        int[] textures = new int[1];GLES20.glGenTextures(1, textures, 0);textureId = textures[0];
  • 創建一個長度為1的整型數組 textures
  • 作用:作為容器接收 OpenGL 生成的紋理 ID
  • 為什么需要數組:因為 glGenTextures 方法需要傳入數組來接收生成的紋理 ID(支持一次性生成多個紋理)
2.創建 SurfaceTexture 和 Surface
        surfaceTexture = new SurfaceTexture(textureId);surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {glSurfaceView.requestRender();}});
...if (preViewSize != null) {surfaceTexture.setDefaultBufferSize(preViewSize.getWidth(),preViewSize.getHeight());}mSurface = new Surface(surfaceTexture);

SurfaceTexture - 紋理消費者

  • 核心作用:將圖像流轉換為 OpenGL ES 紋理

  • 關鍵特性

    • 內部維護一個 BufferQueue(緩沖區隊列)
    • 綁定到 OpenGL 紋理(通過構造函數傳入 textureId
    • 當新幀到達時通知監聽器

    工作流程

// 創建并綁定到OpenGL紋理
SurfaceTexture surfaceTexture = new SurfaceTexture(textureId);
// 設置新幀監聽器
surfaceTexture.setOnFrameAvailableListener(listener);
// 在OpenGL線程更新紋理
surfaceTexture.updateTexImage();  // 將最新幀數據同步到紋理
  • 典型使用者
    • OpenGL ES 渲染器
    • 需要處理圖像流的圖形應用

Surface - 圖像生產者

  • 核心作用:提供圖像數據的寫入接口

  • 關鍵特性

    • SurfaceTexture 的生產者端
    • 實現了 Parcelable,可跨進程傳遞
    • 提供 CanvasBufferQueue 寫入接口
      創建方式:-
    Surface surface = new Surface(surfaceTexture); // 綁定到SurfaceTexture
    
  • 典型生產者

    • 相機 (Camera/Camera2)
    • 視頻解碼器 (MediaPlayer)
    • View 的渲染表面

3. 兩者關系圖解

┌─────────────┐          ┌───────────────────┐          ┌───────────────┐
│             │  writes  │                   │  updates │               │
│  生產者      ├─────────?│      Surface      ├─────────?│ SurfaceTexture │
│ (Camera/解碼器)│          │ (生產者接口)      │          │ (消費者接口)    │
└─────────────┘          └───────────────────┘          └───────┬───────┘│▼┌─────────────────┐│ OpenGL ES Texture││   (textureId)   │└─────────────────┘

簡單流程::

// 步驟1: 創建OpenGL紋理
int textureId = createGLTexture();// 步驟2: 創建SurfaceTexture并綁定紋理  
SurfaceTexture surfaceTexture = new SurfaceTexture(textureId);
surfaceTexture.setOnFrameAvailableListener(() -> {// 新幀到達時請求渲染glSurfaceView.requestRender();
});// 步驟3: 創建Surface作為生產者目標
Surface surface = new Surface(surfaceTexture);// 步驟4: 配置相機輸出到Surface
camera.setPreviewSurface(surface); // Camera1 API
// 或
session.createCaptureSession(..., surface); // Camera2 API// 步驟5: 在渲染器中處理幀
@Override
public void onDrawFrame(GL10 gl) {surfaceTexture.updateTexImage(); // 同步數據到紋理surfaceTexture.getTransformMatrix(texMatrix); // 獲取變換矩陣
// 使用紋理渲染
renderTexture(textureId, texMatrix);}

關鍵區別對比表

特性SurfaceTextureSurface
角色紋理消費者圖像生產者
數據去向OpenGL 紋理綁定到的 SurfaceTexture
核心方法updateTexImage(), getTransformMatrix()lockCanvas(), unlockCanvasAndPost()
是否跨進程是 (實現 Parcelable)
圖形API關聯直接關聯 OpenGL不依賴特定圖形API
主要使用者GL渲染器相機/解碼器/系統渲染服務
  1. 零拷貝機制

    • 數據從生產者→Surface→SurfaceTexture→OpenGL紋理的傳輸不經過CPU內存復制
    • 通過 Android 的 BufferQueue 直接傳遞圖形緩沖區
  2. 紋理坐標系處理

    • 相機幀可能有旋轉/鏡像
    • 需調用 surfaceTexture.getTransformMatrix() 獲取變換矩陣
    • 在著色器中應用矩陣校正:
    attribute vec2 aPosition;
    attribute vec2 aTexCoord;
    uniform mat4 uTexMatrix;
    varying vec2 vTexCoord;void main() {gl_Position = vec4(aPosition, 0.0, 1.0);vTexCoord = (uTexMatrix * vec4(aTexCoord, 0.0, 1.0)).xy;
    }
    
  3. 生命周期管理

    // 正確釋放順序
    camera.stopPreview();
    surface.release();        // 先釋放Surface
    surfaceTexture.release(); // 再釋放SurfaceTexture
    deleteGLTexture(textureId);
    

創建 SurfaceTexture 并綁定紋理
surfaceTexture = new SurfaceTexture(textureId);

  • 作用:創建一個 SurfaceTexture 實例并將其綁定到指定的 OpenGL 紋理
  • 參數textureId - 之前通過 glGenTextures() 生成的 OpenGL 紋理 ID
  • 關鍵機制
    • SurfaceTexture 內部創建一個 BufferQueue
    • 將該隊列與指定的 OpenGL 紋理關聯
    • 后續相機數據將直接流入此紋理

設置幀可用監聽器

surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {glSurfaceView.requestRender();}
});
  • 作用:注冊回調,當新相機幀到達時觸發渲染
  • 工作流程
    1. 相機填充新幀到 SurfaceTexture 的緩沖區
    2. SurfaceTexture 觸發 onFrameAvailable() 回調
    3. 回調中調用 requestRender() 請求 OpenGL 渲染
  • 渲染模式配合
    • 前面設置了 setRenderMode(RENDERMODE_WHEN_DIRTY)
    • 此回調確保有新幀時才渲染,節省資源

設置緩沖區尺寸

if (preViewSize != null) {surfaceTexture.setDefaultBufferSize(preViewSize.getWidth(),preViewSize.getHeight());
}
  • 作用:配置 SurfaceTexture 的緩沖區尺寸以匹配相機預覽分辨率
  • 參數preViewSize - 相機支持的預覽尺寸(如 1920x1080)
  • 為什么重要
    • 確保分配的圖形緩沖區大小正確
    • 避免圖像拉伸/裁剪
    • 優化內存使用和性能

創建 Surface 生產者接口

mSurface = new Surface(surfaceTexture);
  • 作用:創建 Surface 對象作為相機數據輸出的目標
  • 關鍵連接
    • SurfaceSurfaceTexture 的生產者端
    • 相機系統會將幀數據寫入此 Surface
    • 數據自動傳遞到關聯的 OpenGL 紋理
      在這里插入圖片描述
      Surface 和 surfaceTexture 已經創建完成,我們還需要設置GL 的相關。
頂點著色器代碼
String vertexShaderSource ="attribute vec4 aPosition;\n" +"attribute vec2 aTexCoord;\n" +"varying vec2 vTexCoord;\n" +"uniform mat4 uTexMatrix;\n" +"uniform mat4 uMvpMatrix;\n" + // 新增MVP矩陣"void main() {\n" +"    gl_Position = uMvpMatrix * aPosition;\n" + // 應用MVP矩陣"    vTexCoord = (uTexMatrix * vec4(aTexCoord, 0.0, 1.0)).xy;\n" +"}";

attribute vec4 aPosition;

  • attribute:聲明頂點屬性(每個頂點特有的數據
  • vec4:4維向量(x,y,z,w)
  • 作用:接收從CPU傳遞的頂點坐標數據
  • 典型值:屏幕四角的NDC坐標(如[-1,1]范圍)

attribute vec2 aTexCoord;

  • vec2:2維向量(s,t)
  • 作用:接收從CPU傳遞的紋理坐標
  • 典型值[0,0](左下), [1,0](右下), [0,1](左上), [1,1](右上)

varying vec2 vTexCoord;

  • varying聲明插值變量(頂點→片元著色器)
  • 作用:將處理后的紋理坐標傳遞給片元著色器
  • 關鍵特性:在光柵化過程中自動插值

uniform mat4 uTexMatrix;

  • uniform:聲明全局常量(所有頂點共享)
  • mat4:4x4矩陣
  • 作用:校正相機紋理的方向(旋轉/鏡像)
  • 數據來源SurfaceTexture.getTransformMatrix()

uniform mat4 uMvpMatrix; // 新增MVP矩陣

  • 作用:將頂點從模型空間→裁剪空間
  • 組成
    • Model:物體自身變換
    • View:攝像機視角
    • Projection:投影方式(正交/透視)
  • 應用場景:實現2D/3D變換效果
gl_Position = uMvpMatrix * aPosition;
  • gl_Position:內置變量,輸出裁剪空間坐標
  • 計算
    • 應用MVP矩陣變換
    • 可實現旋轉/縮放/3D效果
vTexCoord = (uTexMatrix * vec4(aTexCoord, 0.0, 1.0)).xy;
  • 步驟分解
    1. 將2D紋理坐標擴展為4D向量:vec4(aTexCoord.s, aTexCoord.t, 0.0, 1.0)
    2. 應用紋理變換矩陣 uTexMatrix
    3. 取結果的xy分量 (.xy)
  • 為什么需要
    • 前置攝像頭需要水平翻轉
    • 不同設備旋轉方向不同(0°/90°/180°/270°)
    • 解決紋理坐標與屏幕方向不匹配問題
┌───────────────────────┐     ┌───────────────────┐
│  頂點屬性 (CPU傳入)    │     │   Uniform矩陣      │
│  aPosition: vec4     ├─┬─? │ uMvpMatrix: mat4  │
│  aTexCoord: vec2     │ │   │ uTexMatrix: mat4  │
└───────────────────────┘ │   └───────────────────┘│▼
┌───────────────────────────────────────────────┐
│            頂點著色器處理流程                   │
│  gl_Position = uMvpMatrix * aPosition        │
│  vTexCoord = (uTexMatrix * [aTexCoord]).xy   │
└───────────────────────┬───────────────────────┘│▼
┌───────────────────────────────────────────────┐
│              光柵化插值                        │
│  自動計算每個片元的vTexCoord (插值后的坐標)        │
└───────────────────────┬───────────────────────┘│▼
┌───────────────────────────────────────────────┐
│            片元著色器采樣紋理                    │
│  texture2D(uTexture, vTexCoord)               │
└───────────────────────────────────────────────┘
fragmentShaderSource片段著色器代碼
String fragmentShaderSource ="precision mediump float;\n" +"uniform sampler2D uTextureUnit;\n" +"varying vec2 vTexCoord;\n" +"void main() {\n" +"   vec4 color = texture2D(uTextureUnit, vTexCoord);\n" +"   float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n" +"   gl_FragColor = vec4(gray, gray, gray, color.a);\n" +"}";

主要是varying uniform 等變量屬性寫完著色器代碼 下面就是編譯著色器

編譯著色器
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderSource);int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderSource);private int loadShader(int type, String shaderSource) {int shader = GLES20.glCreateShader(type);GLES20.glShaderSource(shader, shaderSource);GLES20.glCompileShader(shader);// 檢查編譯狀態int[] compiled = new int[1];GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);if (compiled[0] == 0) {String errorMsg = GLES20.glGetShaderInfoLog(shader);GLES20.glDeleteShader(shader);throw new RuntimeException("Shader compile error: " + errorMsg);}return shader;}
創建著色器程序
       // 創建著色器程序programHandle = GLES20.glCreateProgram();GLES20.glAttachShader(programHandle, vertexShader);GLES20.glAttachShader(programHandle, fragmentShader);GLES20.glLinkProgram(programHandle);// 檢查鏈接狀態int[] linkStatus = new int[1];GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);if (linkStatus[0] != GLES20.GL_TRUE) {String errorMsg = GLES20.glGetProgramInfoLog(programHandle);GLES20.glDeleteProgram(programHandle);throw new RuntimeException("Shader program link error: " + errorMsg);}
變量位置查詢

獲取句柄

        positionHandle = GLES20.glGetAttribLocation(programHandle, "aPosition");texCoordHandle = GLES20.glGetAttribLocation(programHandle, "aTexCoord");filterTypeHandle = GLES20.glGetUniformLocation(programHandle, "uFilterType");texMatrixHandle = GLES20.glGetUniformLocation(programHandle, "uTexMatrix");textureHandle = GLES20.glGetUniformLocation(programHandle, "uTexture");mvpMatrixHandle = GLES20.glGetUniformLocation(programHandle, "uMvpMatrix");

在這里插入圖片描述

3. 具體變量解釋

3.1 頂點屬性 (Attributes)

positionHandle = GLES20.glGetAttribLocation(programHandle, "aPosition");
  • 作用:獲取頂點位置屬性的位置句柄
  • 對應著色器變量attribute vec4 aPosition;
  • 使用場景
GLES20.glEnableVertexAttribArray(positionHandle);// 綁定頂點緩沖區數據
GLES20.glVertexAttribPointer(positionHandle, // 位置句柄3,              // 每個頂點分量數 (x,y,z)GLES20.GL_FLOAT,// 數據類型false,          // 是否歸一化12,             // 步長 (3個float * 4字節)vertexBuffer    // 頂點緩沖區
);
texCoordHandle = GLES20.glGetAttribLocation(programHandle, "aTexCoord");
  • 作用:獲取紋理坐標屬性的位置句柄

  • 對應著色器變量attribute vec2 aTexCoord;

  • 使用場景

    GLES20.glEnableVertexAttribArray(texCoordHandle);
    GLES20.glVertexAttribPointer(texCoordHandle,2,              // 兩個紋理坐標分量 (s,t)GLES20.GL_FLOAT,false,8,              // 2個float * 4字節texCoordBuffer
    );
    

統一變量 (Uniforms)

filterTypeHandle = GLES20.glGetUniformLocation(programHandle, "uFilterType");
  • 作用:獲取濾鏡類型統一變量的位置句柄
  • 對應著色器變量uniform int uFilterType;
  • 使用場景
// 設置濾鏡類型 (0=正常, 1=黑白, 2=反色等)
GLES20.glUniform1i(filterTypeHandle, currentFilterType);

texMatrixHandle = GLES20.glGetUniformLocation(programHandle, "uTexMatrix");

  • 作用:獲取紋理變換矩陣的位置句柄
  • 對應著色器變量uniform mat4 uTexMatrix;
  • 使用場景
// 從SurfaceTexture獲取變換矩陣
surfaceTexture.getTransformMatrix(texMatrix);// 傳遞給著色器
GLES20.glUniformMatrix4fv(texMatrixHandle, 1, false, texMatrix, 0);

textureHandle = GLES20.glGetUniformLocation(programHandle, "uTexture");

  • 作用:獲取紋理采樣器的位置句柄

  • 對應著色器變量uniform sampler2D uTexture;

  • 使用場景

    // 激活紋理單元0
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);// 綁定紋理到當前單元
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);// 告訴著色器使用0號紋理單元
    GLES20.glUniform1i(textureHandle, 0);
    

mvpMatrixHandle = GLES20.glGetUniformLocation(programHandle, "uMvpMatrix");

  • 作用:獲取模型-視圖-投影矩陣的位置句柄

  • 對應著色器變量uniform mat4 uMvpMatrix;

  • 使用場景

    // 計算MVP矩陣
    Matrix.setIdentityM(mvpMatrix, 0);
    Matrix.scaleM(mvpMatrix, 0, scaleX, scaleY, 1.0f); // 縮放
    Matrix.translateM(mvpMatrix, 0, transX, transY, 0); // 平移// 傳遞給著色器
    GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);
    
  1. 位置句柄的有效期

    • 只在當前著色器程序鏈接后有效
    • 重新鏈接程序后需要重新獲取
  2. 性能優化

    • 位置句柄只需獲取一次(通常在程序鏈接后)
    • 存儲在成員變量中避免重復查詢
  3. 錯誤處理

    if (positionHandle == -1) {throw new RuntimeException("找不到aPosition屬性");
    }
    
  4. 變量名匹配

    1. 必須與著色器代碼中的變量名完全一致
    2. 大小寫敏感
  5. 著色器優化

    • 未使用的變量可能被編譯器優化掉
    • 返回-1表示變量不存在

在相機預覽渲染中,這些句柄特別重要:

  • texMatrixHandle:校正前置攝像頭鏡像問題
  • textureHandle:綁定相機幀紋理
  • filterTypeHandle:實時切換濾鏡效果
  • mvpMatrixHandle:處理屏幕旋轉適配
設置紋理參數
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(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);

OpenGL ES 中配置外部紋理(用于相機/視頻)的關鍵步驟,專門用于處理 Android 的相機預覽幀或視頻流。

在 Android 相機/視頻處理中,不能使用普通的 GL_TEXTURE_2D,而必須使用特殊的 GL_TEXTURE_EXTERNAL_OES 擴展紋理:

  • 特殊性質:直接接收來自 SurfaceTexture 的流數據
  • 著色器要求:必須聲明擴展 #extension GL_OES_EGL_image_external : require
  • 采樣器類型uniform samplerExternalOES uTexture(不是 sampler2D)
  1. 激活紋理單元

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

    • 作用:激活0號紋理單元(OpenGL ES 有多個紋理單元)
    • 為什么重要
      • OpenGL ES 支持同時使用多個紋理(如 GL_TEXTURE0, GL_TEXTURE1…)
      • 默認激活0號單元(但顯式聲明更安全)
  2. 綁定外部紋理

    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);

    • 關鍵參數
      • GLES11Ext.GL_TEXTURE_EXTERNAL_OES:特殊紋理目標,用于相機/視頻流
      • textureId:之前通過 glGenTextures() 生成的紋理ID
    • 作用
      • 將紋理綁定到當前激活的紋理單元
      • 后續操作將作用于這個紋理
  3. 設置縮小過濾器

    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);

    • 參數解析
      • GL_TEXTURE_MIN_FILTER:紋理縮小過濾方式
      • GL_LINEAR:線性插值(雙線性過濾)
    • 應用場景:當紋理被渲染得比原始尺寸小時(如縮略圖)
  4. 設置放大過濾器

    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

    • 參數解析
      • GL_TEXTURE_MAG_FILTER:紋理放大過濾方式
    • 為什么用線性過濾
      • 提供平滑的圖像質量
      • 最適合相機預覽(比 GL_NEAREST 鋸齒感少)
  5. 設置S方向(水平)環繞模式

    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);

    • 參數解析
      • GL_TEXTURE_WRAP_S:水平方向(U坐標)
      • GL_CLAMP_TO_EDGE:邊緣像素延伸
    • 為什么不用重復
      • 相機幀不需要平鋪重復
      • 防止邊緣出現異常顏色
  6. 設置T方向(垂直)環繞模式

    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

    • 同上,作用于垂直方向(V坐標)

外部紋理 vs 普通2D紋理

特性GL_TEXTURE_EXTERNAL_OESGL_TEXTURE_2D
來源相機/視頻流圖像數據
創建glGenTextures() + 特殊綁定常規創建
數據上傳通過 SurfaceTexture 自動更新glTexImage2D()
著色器聲明#extension... + samplerExternalOESsampler2D
Mipmap不支持支持
坐標范圍必須 [0,1]可重復
性能零拷貝,高效需要數據復制

為什么需要這些設置?

  1. 過濾模式 (GL_LINEAR)
    • 相機幀常需要縮放(如適配屏幕)
    • 線性過濾提供最自然的視覺效果
    • 避免 GL_NEAREST 產生的像素化鋸齒
  2. 環繞模式 (GL_CLAMP_TO_EDGE)
    • 相機幀是連續視頻流,不是平鋪紋理
    • 防止邊緣采樣錯誤(尤其旋轉時)
    • 兼容所有 Android 設備(某些設備嚴格需要)
  3. 外部紋理的特殊性
    • 直接映射到 SurfaceTexture 的緩沖區
    • 避免 CPU-GPU 數據拷貝(零拷貝)
    • 支持 YUV 到 RGB 的硬件轉換

常見問題解決

紋理顯示綠色或扭曲?

  1. 檢查著色器是否正確定義 samplerExternalOES
  2. 確認調用了 surfaceTexture.updateTexImage()
  3. 驗證紋理坐標變換矩陣的使用:
surfaceTexture.getTransformMatrix(texMatrix);

性能優化提示

  • 紋理配置只需一次(放在初始化時)
  • 避免每幀重復調用這些參數設置
  • 使用 RENDERMODE_WHEN_DIRTY 模式

setIdentityM(float[] sm, int smOffset)

方法將數組 sm 中從偏移量 smOffset 開始的16個元素(代表4x4矩陣)設置為單位矩陣。

在圖形編程中的作用:

  • 初始化MVP矩陣:在OpenGL渲染前,通常將模型視圖投影矩陣(MVP)初始化為單位矩陣,作為變換計算的起點。
  • 重置變換:單位矩陣表示“無變換”狀態,后續的平移、旋轉、縮放操作會基于此矩陣累積。
  • 矩陣運算基準:類似于乘法中的"1",確保矩陣操作從初始狀態開始。
// 初始化MVP矩陣為單位矩陣
float[] mvpMatrix = new float[16];
Matrix.setIdentityM(mvpMatrix, 0); // 后續操作(例如平移)會基于此單位矩陣
Matrix.translateM(mvpMatrix, 0, 0, 0, -5); // 沿z軸平移-5
onSurfaceChanged

GLSurfaceView.Renderer接口中的一個方法,當Surface尺寸改變時系統自動調用。
設置OpenGL視口

GLES20.glViewport(0, 0, width, height);
  • 作用:定義OpenGL的渲染區域(窗口坐標系)
  • 參數
    • 前兩個參數:視口左下角坐標(0,0表示從屏幕左下角開始)
    • 后兩個參數:視口寬度和高度(使用新的窗口尺寸)
  • 重要性:當屏幕旋轉或窗口大小改變時,必須重新設置視口,否則渲染會變形或錯位
    為什么需要這個回調?
  1. 設備方向變化:當手機旋轉時(豎屏?橫屏)
  2. 窗口大小改變:分屏模式、折疊屏切換等
  3. 初始化時:Surface首次創建時也會調用

后續關鍵應用:

這個寬高比主要用于投影矩陣的計算,確保3D場景正確顯示:

// 在onDrawFrame中通常會這樣使用
Matrix.perspectiveM(projectionMatrix, 0, 45f,            // 視野角度viewAspectRatio, // 這里使用計算的寬高比0.1f, 100f);    // 近/遠裁剪平面

典型工作流程:

onSurfaceCreated (初始化) → onSurfaceChanged (尺寸確定) → onDrawFrame (渲染循環)

不處理的后果:

  • 豎屏轉橫屏時:物體會被壓縮變扁
  • 分屏模式:只渲染部分區域
  • 折疊屏展開:畫面只顯示在部分屏幕
onDrawFrame

OpenGL ES渲染循環的核心部分,主要負責每一幀的渲染準備工作

設置清除顏色

GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  • 作用:設置清除屏幕時使用的背景顏色
  • 參數:RGBA顏色值(紅、綠、藍、透明度)
  • 本例:黑色(RGB=0)且完全不透明(Alpha=1.0)

清除顏色緩沖區

GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
  • 作用:用預設的清除顏色填充整個屏幕
  • GL_COLOR_BUFFER_BIT:指定清除顏色緩沖區
  • 效果:將屏幕重置為純黑色,擦除上一幀內容

更新紋理數據

surfaceTexture.updateTexImage();
  • 作用:從SurfaceTexture獲取最新的圖像幀并更新到OpenGL紋理
  • 典型應用:用于顯示相機預覽、視頻流或動態生成的圖像
  • 工作原理:從Android的SurfaceTexture中提取最新的圖像數據,將其綁定到OpenGL紋理

獲取紋理變換矩陣
4.

surfaceTexture.getTransformMatrix(texMatrix);
  • 作用:獲取紋理坐標變換矩陣
  • 為什么需要:相機/視頻源的圖像方向可能與設備方向不一致
  • 功能
    • 校正圖像旋轉(如手機豎屏時相機橫屏拍攝)
    • 處理鏡像翻轉(前置攝像頭通常需要)
    • 調整UV坐標映射

整體流程說明:

  1. 重置畫布:用黑色清屏(準備繪制新幀)
  2. 獲取新幀:從視頻源/相機獲取最新圖像
  3. 準備紋理:將新圖像轉換為OpenGL可用的紋理
  4. 校正顯示:計算紋理變換矩陣,確保圖像正確顯示
    private void drawTexture() {// 更新MVP矩陣updateMvpMatrix();// 使用著色器程序GLES20.glUseProgram(programHandle);// 啟用頂點屬性數組GLES20.glEnableVertexAttribArray(positionHandle);GLES20.glEnableVertexAttribArray(texCoordHandle);// 傳遞頂點數據vertexBuffer.position(0);GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);// 傳遞紋理坐標數據texCoordBuffer.position(0);GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, texCoordBuffer);// 傳遞MVP矩陣if (mvpMatrixHandle != -1) {GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);}// 傳遞紋理變換矩陣GLES20.glUniformMatrix4fv(texMatrixHandle, 1, false, texMatrix, 0);// 設置紋理單元if (filterTypeHandle != -1) {GLES20.glUniform1i(filterTypeHandle, filterType);}// 繪制GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);// 禁用頂點屬性數組GLES20.glDisableVertexAttribArray(positionHandle);GLES20.glDisableVertexAttribArray(texCoordHandle);}private void updateMvpMatrix() {// 重置為單位矩陣Matrix.setIdentityM(mvpMatrix, 0);if (previewAspectRatio > viewAspectRatio) {// 預覽比視圖寬,縮放高度float scale = viewAspectRatio / previewAspectRatio;Matrix.scaleM(mvpMatrix, 0, 1f, scale, 1f);} else {// 預覽比視圖高,縮放寬度float scale = previewAspectRatio / viewAspectRatio;Matrix.scaleM(mvpMatrix, 0, scale, 1f, 1f);}}

updateMvpMatrix()
作用是根據預覽內容(如相機畫面)和視圖區域的寬高比差異,計算并更新模型視圖投影矩陣(MVP Matrix),以實現畫面自適應縮放,保持原始比例不變形。

代碼根據兩種寬高比的關系動態調整縮放:

  • previewAspectRatio:預覽內容的寬高比(寬度/高度)
  • viewAspectRatio:視圖區域的寬高比(寬度/高度)
矩陣操作詳解:

初始化單位矩陣

Matrix.setIdentityM(mvpMatrix, 0);

應用縮放變換

Matrix.scaleM(mvpMatrix, 0, scaleX, scaleY, scaleZ);

實際應用場景:

// 在渲染前調用
updateMvpMatrix();// 將矩陣傳入著色器
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);

頂點著色器中使用:

gl_Position = uMVPMatrix * aPosition;

為什么需要這樣做?

  1. 避免拉伸變形:直接拉伸會扭曲圖像(圓形變橢圓)
  2. 保持內容完整性:完整顯示原始畫面內容
  3. 自適應不同屏幕:處理手機旋轉/分屏/折疊屏等場景

這種處理是視頻播放器、相機預覽等應用的標配邏輯,數學上屬于保持原始比例的仿射變換。通過調整MVP矩陣而非直接修改頂點坐標,可以利用GPU的并行計算優勢提高性能。

GLES20.glUseProgram(programHandle);

GLES20.glUseProgram(programHandle); 是OpenGL ES 2.0中一個關鍵的函數調用,用于激活指定的著色器程序。以下是詳細解釋:

作用與功能

  1. 激活著色器程序
    • 將指定的著色器程序設置為當前渲染管線使用的程序
    • 所有后續的繪制操作都將使用這個程序中的著色器
  2. 參數說明
    • programHandle:指向著色器程序的整數句柄(ID)
    • 這個句柄是通過之前glCreateProgram()glLinkProgram()創建的

工作原理

// 創建著色器程序
int programHandle = GLES20.glCreateProgram();// 附加著色器(頂點+片段)
GLES20.glAttachShader(programHandle, vertexShader);
GLES20.glAttachShader(programHandle, fragmentShader);// 鏈接程序
GLES20.glLinkProgram(programHandle);// 使用程序
GLES20.glUseProgram(programHandle); // <-- 關鍵調用

底層機制

當調用glUseProgram()時:

  1. GPU驅動加載指定程序的字節碼
  2. 配置渲染管線階段:
    • 頂點處理器使用頂點著色器
    • 片段處理器使用片段著色器
  3. 重置所有uniform變量為默認值
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(texCoordHandle);

功能詳解:

  1. 啟用頂點屬性數組
    • 告訴OpenGL:“請使用我提供的數組數據,而不是默認的常量值”
    • 默認情況下,所有頂點屬性都是禁用的,使用常量值(0,0,0,1)
  2. 參數說明
    • positionHandle:頂點位置屬性的句柄(從著色器獲取)
    • texCoordHandle:紋理坐標屬性的句柄(從著色器獲取)

完整工作流程:

// 1. 獲取屬性位置(通常在初始化時完成)
positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
texCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord");// 2. 指定頂點數據來源(在繪制前調用)
GLES20.glVertexAttribPointer(positionHandle,  // 屬性位置3,              // 每個頂點的分量數(x,y,z)GLES20.GL_FLOAT, // 數據類型false,          // 是否歸一化5 * 4,          // 步長(每個頂點占20字節:3位置+2紋理坐標)*4字節/floatvertexBuffer    // 頂點緩沖區
);GLES20.glVertexAttribPointer(texCoordHandle, 2,              // 每個紋理坐標的分量數(u,v)GLES20.GL_FLOAT, false, 5 * 4, vertexBuffer.position(3) // 從緩沖區第12字節開始(3個float后)
);// 3. 啟用屬性數組
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(texCoordHandle);// 4. 繪制圖形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);// 5. 可選:禁用屬性數組(減少資源占用)
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(texCoordHandle);

概念說明
頂點屬性(Attribute)著色器中每個頂點的輸入數據
屬性句柄(Location)著色器中屬性的唯一標識符
頂點緩沖區(VBO)存儲頂點數據的GPU內存區域
glVertexAttribPointer定義如何從緩沖區讀取數據
vertexBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
  1. 重置緩沖區位置

    vertexBuffer.position(0);
    
    • 將頂點緩沖區的讀取位置重置到開頭
    • 確保從緩沖區的起始位置讀取數據
  2. 設置頂點屬性指針

    GLES20.glVertexAttribPointer(positionHandle,    // 著色器中頂點位置屬性的句柄3,                // 每個頂點包含的分量數 (x,y,z)GLES20.GL_FLOAT,  // 數據類型為浮點數false,            // 不需要歸一化處理0,                // 連續頂點間的字節步長 (0表示緊密排列)vertexBuffer      // 包含頂點數據的緩沖區
    );
    
參數說明
屬性句柄通過glGetAttribLocation獲取的著色器屬性位置
分量數位置屬性需要3個值(x,y,z),紋理坐標需要2個值(u,v)
數據類型GL_FLOAT表示使用32位浮點數
歸一化false表示保持原始值范圍,不壓縮到[0,1]
步長(Stride)0表示數據緊密排列,無間隔
緩沖區包含實際數據的Java NIO緩沖區

典型的頂點數據結構:

// 頂點位置數據 (每個頂點3個float)
float[] vertexData = {-1.0f, -1.0f, 0.0f,  // 左下1.0f, -1.0f, 0.0f,  // 右下-1.0f,  1.0f, 0.0f,  // 左上1.0f,  1.0f, 0.0f   // 右上
};// 紋理坐標數據 (每個坐標2個float)
float[] texCoordData = {0.0f, 1.0f,  // 左下1.0f, 1.0f,  // 右下0.0f, 0.0f,  // 左上1.0f, 0.0f   // 右上
};

工作流程

  1. 準備數據

    // 創建頂點緩沖區
    FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(VERTEX_DATA.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    vertexBuffer.put(vertexData);
    vertexBuffer.position(0);// 創建紋理坐標緩沖區(類似)
    
  2. 設置屬性指針(如上述代碼)

  3. 啟用屬性數組

    GLES20.glEnableVertexAttribArray(positionHandle);
    GLES20.glEnableVertexAttribArray(texCoordHandle);
    
  4. 繪制圖形

    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    

矩陣傳遞給頂點著色器

        // 傳遞MVP矩陣if (mvpMatrixHandle != -1) {GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);}// 傳遞紋理變換矩陣GLES20.glUniformMatrix4fv(texMatrixHandle, 1, false, texMatrix, 0);
  • 作用:將模型-視圖-投影(Model-View-Projection)矩陣傳遞給頂點著色器

  • 參數解析

    • mvpMatrixHandle:著色器中MVP矩陣的uniform位置句柄
    • 1:傳遞的矩陣數量
    • false:不轉置矩陣(OpenGL ES默認列主序)
    • mvpMatrix:包含16個float值的矩陣數組
    • 0:矩陣數據在數組中的偏移量
  • 檢查!= -1:確保著色器中實際存在這個uniform變量

  • 在渲染中的作用

    • 將3D頂點從模型空間轉換到裁剪空間
    • 綜合了模型變換、相機視圖和投影變換
  • 作用:將紋理坐標變換矩陣傳遞給著色器

  • 參數解析

    • texMatrixHandle:紋理變換矩陣的uniform位置句柄
    • texMatrix:從SurfaceTexture獲取的紋理變換矩陣
  • 特殊用途

    • 校正相機預覽的方向(如前置攝像頭鏡像)
    • 處理設備旋轉時的圖像方向
    • 調整不同寬高比的紋理映射

頂點著色器示例

uniform mat4 uMVPMatrix;      // MVP矩陣
uniform mat4 uTexMatrix;      // 紋理變換矩陣attribute vec4 aPosition;     // 頂點位置
attribute vec4 aTextureCoord; // 原始紋理坐標varying vec2 vTextureCoord;   // 傳遞給片段著色器的紋理坐標void main() {gl_Position = uMVPMatrix * aPosition;vTextureCoord = (uTexMatrix * aTextureCoord).xy;
}
概念說明
MVP矩陣綜合模型、視圖、投影變換的矩陣
紋理變換矩陣校正紋理坐標的特殊變換
glUniformMatrix4fv傳遞4x4矩陣到著色器的函數
uniform變量著色器中保持不變的值(每幀設置一次)

常見問題解決方案

  1. 圖像顯示不正確
    • 檢查矩陣計算邏輯
    • 確保矩陣傳遞順序正確
    • 驗證著色器中的矩陣運算
  2. 紋理方向錯誤
    • 確保texMatrix從SurfaceTexture正確獲取
    • 檢查設備方向處理邏輯
  3. 性能優化
    • 只在矩陣變化時更新
    • 避免每幀重復計算不變矩陣

提示:在相機預覽等實時應用中,texMatrix通常每幀變化(處理設備旋轉),而mvpMatrix在視圖尺寸不變時可緩存復用。這兩個矩陣的協同工作確保了3D空間正確投影和紋理正確映射。

選擇濾鏡

if (filterTypeHandle != -1) {GLES20.glUniform1i(filterTypeHandle, filterType);
}
  • 作用:將當前選擇的濾鏡類型傳遞給片段著色器
  • 參數
    • filterTypeHandle:著色器中濾鏡類型uniform的句柄
    • filterType:整數表示的濾鏡類型(如0=正常,1=黑白,2=復古等)
  • 技術細節
    • glUniform1i:傳遞單個整數值到著色器
    • != -1檢查:確保著色器中存在該uniform變量

執行繪制命令

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
  • 作用:命令GPU執行實際的渲染操作
  • 參數詳解
    • GL_TRIANGLE_STRIP:三角形條帶繪制模式
    • 0:從頂點數組的第一個索引開始
    • 4:總共繪制4個頂點(兩個三角形)
  • 渲染結果
    • 繪制一個矩形(兩個三角形組成)
    • 每個頂點包含位置和紋理坐標
    • 應用當前設置的濾鏡效果
0---2
| / |  頂點順序:0→1→2→3
| / |  三角形1:0-1-2
1---3  三角形2:1-2-3

禁用頂點屬性數組

GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(texCoordHandle);
  • 作用:釋放頂點屬性數組資源
  • 為什么需要
    • 避免資源泄露
    • 防止后續繪制操作意外使用當前配置
    • 減少GPU狀態保持開銷
  • 專業實踐
    • 在繪制完成后立即禁用
    • glEnableVertexAttribArray成對出現
    • 特別是多對象渲染時必不可少

性能優化提示

  1. 濾鏡切換優化
    • 只在濾鏡改變時更新uniform
    • 避免每幀重復設置相同值
  2. 批處理繪制
    • 相同濾鏡的物體一起繪制
    • 減少著色器切換次數
  3. 資源復用
    • 頂點緩沖區對象(VBO)長期保留
    • 著色器程序預編譯
  4. 避免冗余調用
    • 檢查uniform句柄有效性(-1檢查)
    • 只在必要時更新矩陣

目前從重寫GLSurfaceView 到自定義Rander中onCreate ->onSurfaceChanged ->onDrawFrame 已完全實現,所以整體上已完成。我們需要將生成的surface 傳遞到相機中。
也就是創建session 中需要配置的surface 列表中,和請求預覽時 addTarget 中。后續預覽就會顯示到我們設置view中了。

final CaptureRequest.Builder previewRequestBuilder =cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);previewRequestBuilder.addTarget(previewSurface);cameraDevice.createCaptureSession(Arrays.asList(previewSurface),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {captureSession = session;try {// 設置重復請求captureSession.setRepeatingRequest(previewRequestBuilder.build(),null, null);} catch (CameraAccessException e) {e.printStackTrace();}}···
}                        

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88771.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88771.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88771.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CAS教務系統單點登錄分析

1. 核心 URL 結構 GET /authserver/login?servicehttp%3A%2F%2F192.168.254.188%2Fjsxsd%2Fframework%2FxsMainV.htmlx認證服務器&#xff1a;authserver.XXXX.edu.cn&#xff08;典型 CAS 系統&#xff09;目標服務&#xff1a;http://192.168.254.188/jsxsd/framework/xsMa…

利用云霧自動化在智能無人水面航行器中實現自主碰撞檢測和分類

大家覺得有讀完覺得有幫助記得關注和點贊&#xff01;&#xff01;&#xff01; 抽象 工業信息物理系統 &#xff08;ICPS&#xff09; 技術是推動海上自主化的基礎&#xff0c;尤其是對于無人水面航行器 &#xff08;USV&#xff09;。然而&#xff0c;船上計算限制和通信延遲…

AI+物聯網:從萬物互聯到萬物智聯

AI物聯網&#xff1a;從萬物互聯到萬物智聯的范式革命 當農田傳感器自主決策灌溉時機&#xff0c;當咖啡機根據睡眠數據調節濃度&#xff0c;當城市交通系統在擁堵發生前主動干預——這些場景不再是科幻想象&#xff0c;而是2025年AIoT&#xff08;人工智能物聯網&#xff09;…

Python爬蟲實戰:研究Levenshtein庫相關技術

1. 引言 1.1 研究背景與意義 隨著電子商務的快速發展,網絡上積累了海量的產品數據。這些數據來自不同的電商平臺、賣家,存在著產品名稱不統一、規格描述差異大等問題,給數據整合、價格比較、競品分析等應用帶來了極大挑戰。傳統的精確匹配方法無法處理產品名稱中的拼寫錯誤…

MySQL 總是差八個小時,如何破?

MySQL 總是差八個小時&#xff0c;如何破&#xff1f;_mysql__江南一點雨-Byzer 白澤 解決 SpringBoot 應用中 MySQL 時區配置引起的時間不一致問題 - 路有所思 - 博客園

iOS 為圖片添加水印

(instancetype)waterMarkWithImage:(UIImage *)image andMarkImageName:(NSString *)markName{ UIImage *watermarkImage [UIImage imageNamed:markName]; if (!watermarkImage) { NSLog("水印圖片加載失敗: %", markName); return image; } // 獲取原圖尺寸和方向 …

藍牙工作頻段與跳頻擴頻技術(FHSS)詳解:面試高頻考點與真題解析

藍牙技術憑借其低功耗、短距離通信的特性,已成為物聯網、智能穿戴等領域的核心技術之一。其核心競爭力在于對 2.4GHz ISM 頻段的高效利用與跳頻擴頻技術(FHSS)的創新應用。本文將系統梳理藍牙工作頻段與 FHSS 的高頻考點,并結合歷年真題與解析,快速掌握核心知識,輕松應對…

ArkTS與倉頡開發語言:鴻蒙編程的雙子星

前言 鴻蒙是多語言生態&#xff0c;ArkTS、倉頡和 C/C充分互補。ArkTS 是動態類型編程語言&#xff0c;主打易學易用、生態豐富、極簡開發、持續創新四大特征&#xff1b;倉頡是靜態類型編程語言&#xff0c;主打高性能、強安全、跨平臺、智能化等特性。為滿足不同業務場景訴求…

怎么把本地倉庫push 到gitlab 上

1. 首先 我們需要再gitlab 上建立一個group &#xff0c; &#xff08;group 可也設定是public 還是private&#xff09;&#xff0c;public 可以不用用戶密碼用 https 下載 2. 再gitlab 的group 下 建立一個倉庫 &#xff08;pulbic/private) 如何刪除 一個倉庫&#xff08;…

論文筆記(八十六)V-HOP: Visuo-Haptic 6D Object Pose Tracking

V-HOP: Visuo-Haptic 6D Object Pose Tracking 文章概括摘要1. 引言2.背景A. 問題定義B. 觸覺表示基于單元陣列的傳感器基于視覺的傳感器 3. 方法學A. 手爪表示B. 物體表示C. 網絡設計D. 訓練范式 IV. 實驗A. 多形態數據集B. 位姿跟蹤比較C. 模態消融D. 融合策略消融E. 遮擋對性…

[論文閱讀] (40)CCS24 PowerPeeler:一種通用的PowerShell腳本動態去混淆方法

《娜璋帶你讀論文》系列主要是督促自己閱讀優秀論文及聽取學術講座&#xff0c;并分享給大家&#xff0c;希望您喜歡。由于作者的英文水平和學術能力不高&#xff0c;需要不斷提升&#xff0c;所以還請大家批評指正&#xff0c;非常歡迎大家給我留言評論&#xff0c;學術路上期…

DeepSeek工具對AI編程幫助

一、技術架構&#xff1a;混合專家模型&#xff08;MoE&#xff09;的顛覆性優勢 DeepSeek的核心競爭力源于其混合專家模型架構&#xff08;Mixture of Experts&#xff09;&#xff1a; 參數規模&#xff1a;6710億參數&#xff0c;每個token僅激活37億參數&#xff0c;實現超…

鏈表題解——兩數相加【LeetCode】

方法一&#xff1a;遞歸 寫法一&#xff1a;創建新節點 算法思路解析 該實現采用 遞歸方式 逐位處理兩個鏈表&#xff0c;并考慮進位 carry&#xff1a; ? 步驟拆解 遞歸終止條件&#xff1a;當 l1, l2 都為空且沒有進位&#xff08;carry 0&#xff09;&#xff0c;說明…

AutoGen框架的ReAct推理模式的多跳測試

問題&#xff1a;特斯拉公司 CEO 的出生地是哪個國家&#xff1f; 答案&#xff1a;南非。 推理過程&#xff1a; 第一跳&#xff1a;確定特斯拉&#xff08;Tesla, Inc.&#xff09;的 CEO。特斯拉的 CEO 是埃隆馬斯克&#xff08;Elon Musk&#xff09;。 第二跳&#xff1a;…

MCP-安全(entra)

保護 AI 工作流程&#xff1a;模型上下文協議服務器的 Entra ID 身份驗證 介紹 保護模型上下文協議 (MCP) 服務器的安全與鎖好家門一樣重要。保持 MCP 服務器開放會導致您的工具和數據遭受未經授權的訪問&#xff0c;從而導致安全漏洞。Microsoft Entra ID 提供強大的基于云的身…

Node.js特訓專欄-實戰進階:8. Express RESTful API設計規范與實現

?? 歡迎來到 Node.js 實戰專欄!在這里,每一行代碼都是解鎖高性能應用的鑰匙,讓我們一起開啟 Node.js 的奇妙開發之旅! Node.js 特訓專欄主頁 專欄內容規劃詳情 Express RESTful API設計規范與實現:構建標準化、可維護的接口服務 在前后端分離架構盛行的今天,RESTful A…

2025企業數字化轉型之道

進入2025年&#xff0c;企業的數字化轉型已經不再是選擇題&#xff0c;而是生存和發展的關鍵。如何抓住技術的浪潮&#xff0c;提高效率、提升客戶體驗、加強創新&#xff0c;成了企業亟需解決的問題。 1.自動化&#xff1a;釋放人力潛力 自動化是數字化轉型的起點。通過RPA&a…

TCP 保活定時器詳解:原理、配置與最佳實踐

一、TCP 保活定時器基礎原理 TCP 保活定時器&#xff08;TCP Keepalive Timer&#xff09;是 TCP 協議中用于檢測長時間無數據傳輸的連接是否仍然有效的機制。它通過在連接空閑一段時間后發送探測報文&#xff0c;確認對方主機是否仍然可達&#xff0c;從而避免在對端異常斷開…

瀏覽器工作原理27 [#]PWA:解決了web應用哪些問題

引用 《瀏覽器工作原理與實踐》 PWA&#xff0c;全稱是 Progressive Web App &#xff0c;翻譯過來就是漸進式網頁應用。根據字面意思&#xff0c;它就是“漸進式 Web 應用”。對于 Web 應用很好理解了&#xff0c;就是目前普通的 Web 頁面&#xff0c;所以 PWA 所支持的首先是…

Leetcode百題斬-圖論

再開下一個坑&#xff0c;圖論專題居然以前都刷過了&#xff0c;三道Medium也沒什么好說的&#xff0c;直接過 994. Rotting Oranges[Medium] 發現一個很神奇的事&#xff0c;這一題我再5年前的時候做&#xff0c;還是個Easy&#xff0c;現在已經漲到Medium了。看來隨著通貨膨…