Android平台Camera实时滤镜实现方法探讨(三)--通过Shader实现YUV转换RBG

文章例如该链接通过将YUV分成三个纹理,在shader中取出并且经过公式变换,转换成RGB。我尝试了下,显示的是灰色的,可能是这篇文章采用的是planar格式的YUV,与Android平台的packed格式的YUV不同,因此需要在纹理绑定处进行数据指针的修改


之前在一篇13年北大硕士的论文基于android平台实时滤镜的设计与实现中提出了一种实现方法,采用双通道,将Y通道与UV通道分别贴图。网上也有单通道经过一些转换再转换的方法,欢迎讨论。


首先我们探讨下YUV格式

Android平台相机预览数据获取接口onPreviewFrame中默认获取的是YUV420sp格式,例如下图为8X4像素的YUV图像示意图



即首先将Y信号排列,然后UV数据分别交错排列。其中Y信号数组长度为width * height,UV信号长度为width * heght / 2,数组首元素位置起始于width * height。总长度为width * height * 1.5,相比于采用传统的rgb格式长度减少一半,因此常用语电视信号传输。


其中Y表示明亮度,也就是灰阶值。UV表示色度,是描述影响色彩及饱和度,用于指定像素颜色。因此,如果我们只使用Y通道,看到的就是原图的灰度图。


因为GPU并不会根据传入的纹理判断格式,所以我们可以将YUV数据作为RGB数据欺骗GPU,将Y通道与UV通道分成两个纹理传入shader,在shader中利用GPU的优势来进行快速转换。注意要使用两个不同的纹理单元,例如GL_TEXTURE0和GL_TEXTURE1,同样修改glUniform1i第二个参数

代码:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, id_y);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
glUniform1i(gvImageTextureY, 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, id_uv);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width/2, height/2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data + width*height);
glUniform1i(gvImageTextureUV, 1);


与直接使用RBGA数据不同,这里的参数采用的是GL_LUMINANCE,与GL_LUMINANCE_ALPHA,与GL_RGBA不同,GL_RGBA单独保存R、G、B、A四个数据,而GL_LUMINANCE将这四个数据合并成一个,因为这样1个Y就可以与1个RGBA对应。GL_LUMINANCE_ALPHA代表首先是亮度,然后是alpha值,这样我们就能将U值与V值分别取出。


之后通过shader将YUV格式转为RGB格式:

precision mediump float;
uniform sampler2D mGLUniformTexture;
uniform sampler2D mGLUniformTexture1;
varying highp vec2 textureCoordinate;

const mat3 yuv2rgb = mat3(
	1, 0, 1.2802,
	1, -0.214821, -0.380589,
	1, 2.127982, 0
);

void main() {
	vec3 yuv = vec3(
		1.1643 * (texture2D(mGLUniformTexture, textureCoordinate).r - 0.0625),
		texture2D(mGLUniformTexture1, textureCoordinate).a - 0.5,
		texture2D(mGLUniformTexture1, textureCoordinate).r - 0.5
	);\
	vec3 rgb = yuv * yuv2rgb;
	gl_FragColor = vec4(rgb, 1);
}


其中,texture2D(mGLUniformTexture, textureCoordinate).r即YUV中的Y数据,texture2D(mGLUniformTexture, textureCoordinate).a即YUV中的V数据,剩下一个就是U,经过矩阵公式转换后就是RGB数据,然后设置给gl_FragColor,OpenGL就可以正确的显示了


其余部分基本不变,也不再赘述


  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
实现实时二维码扫描功能,可以使用 Androidcamera2 API 和 Google 的 ZXing 库。 首先,在 AndroidManifest.xml 文件中添加相机和网络权限: ```xml <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> ``` 然后,在 build.gradle 文件中添加 ZXing 库的依赖项: ```gradle implementation 'com.google.zxing:core:3.4.0' implementation 'com.journeyapps:zxing-android-embedded:3.6.0' ``` 接下来,在布局文件中添加一个 TextureView 组件用于预览相机图像,并在代码中实例化 CameraManager 和 TextureView: ```xml <TextureView android:id="@+id/textureView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` ```java private CameraManager cameraManager; private TextureView textureView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE); textureView = findViewById(R.id.textureView); } ``` 然后,实现 TextureView.SurfaceTextureListener 接口,以在 TextureView 就绪时打开相机,开始预览图像: ```java private String cameraId; private CameraDevice cameraDevice; private Size previewSize; private CaptureRequest.Builder captureRequestBuilder; private CameraCaptureSession cameraCaptureSession; private ImageReader imageReader; private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { openCamera(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { } }; ``` 在 openCamera() 方法中,获取可用的相机列表并选择一个相机,设置预览尺寸和输出格式,然后打开相机: ```java private void openCamera() { try { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); assert map != null; previewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight()); imageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); imageReader.setOnImageAvailableListener(imageReaderListener, backgroundHandler); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return; } cameraManager.openCamera(cameraId, stateCallback, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } ``` 在 stateCallback 中处理相机设备的状态,如果打开成功,创建一个 CaptureRequest.Builder 对象,用于构建捕获请求: ```java private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { cameraDevice = camera; createPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { cameraDevice.close(); cameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { cameraDevice.close(); cameraDevice = null; } }; private void createPreviewSession() { try { SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); assert surfaceTexture != null; surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface previewSurface = new Surface(surfaceTexture); Surface imageReaderSurface = imageReader.getSurface(); captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(previewSurface); captureRequestBuilder.addTarget(imageReaderSurface); cameraDevice.createCaptureSession(Arrays.asList(previewSurface, imageReaderSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { cameraCaptureSession = session; updatePreview(); } @Override public void onConfigureFailed(CameraCaptureSession session) { } }, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } ``` 在 updatePreview() 方法中,设置捕获请求的参数并开始预览: ```java private void updatePreview() { try { captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } ``` 最后,在 imageReaderListener 中处理相机输出的图像数据,解码二维码并将结果显示在界面上: ```java private final ImageReader.OnImageAvailableListener imageReaderListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); image.close(); try { PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, previewSize.getWidth(), previewSize.getHeight(), 0, 0, previewSize.getWidth(), previewSize.getHeight(), false); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); Result result = new MultiFormatReader().decode(bitmap); runOnUiThread(() -> textView.setText(result.getText())); } catch (Exception e) { e.printStackTrace(); } } }; ``` 完整的代码示例可以参考 https://github.com/edwardw1987/Camera2QRCodeScanner 。注意,在实际开发中,可能需要进行更多的错误处理和性能优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值