AR相机的实现
来源:互联网 发布:网络大电影营销 编辑:程序博客网 时间:2024/05/19 01:31
首先需要声明的是,我并不是原创每个概念,而是在前人的基础上做了一个实现,原理如图:
上面的图片来自AR红包Android端实现原理
上面的难点我觉得可能是处理一帧图像的方法,这个方法有两个,分别是Camera和Camera2提供的,先看看Camera2的示例:
// 开始预览,主要是camera.createCaptureSession这段代码很重要,创建会话 private void startPreview(CameraDevice camera) throws CameraAccessException { SurfaceTexture texture = mPreviewView.getSurfaceTexture(); // 这里设置的就是预览大小 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());</span> Surface surface = new Surface(texture); try { // 设置捕获请求为预览,这里还有拍照啊,录像等 mPreviewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); } catch (CameraAccessException e) { e.printStackTrace(); } // 就是在这里,通过这个set(key,value)方法,设置曝光啊,自动聚焦等参数!! 如下举例: // mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); mImageReader = ImageReader.newInstance(mSurfaceView.getWidth(), mSurfaceView.getHeight(), ImageFormat.JPEG/*此处还有很多格式,比如我所用到YUV等*/, 2/*最大的图片数,mImageReader里能获取到图片数,但是实际中是2+1张图片,就是多一张*/); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler); // 这里一定分别add两个surface,一个Textureview的,一个ImageReader的,如果没add,会造成没摄像头预览,或者没有ImageReader的那个回调!! mPreviewBuilder.addTarget(surface); mPreviewBuilder.addTarget(mImageReader.getSurface()); mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),mSessionStateCallback, mHandler); } private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { try { updatePreview(session); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { } }; private void updatePreview(CameraCaptureSession session) throws CameraAccessException { session.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler); } private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { /** * 当有一张图片可用时会回调此方法,但有一点一定要注意: * 一定要调用 reader.acquireNextImage()和close()方法,否则画面就会卡住!!!!!我被这个坑坑了好久!!! * 很多人可能写Demo就在这里打一个Log,结果卡住了,或者方法不能一直被回调。 **/ @Override public void onImageAvailable(ImageReader reader) { Image img = reader.acquireNextImage(); /** * 因为Camera2并没有Camera1的Priview回调!!!所以该怎么能到预览图像的byte[]呢?就是在这里了!!!我找了好久的办法!!! **/ ByteBuffer buffer = img.getPlanes()[0].getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); img.close(); } }; }
上述方法也可以实现图像的处理,但是我使用红米以及小米5在测试过程中发现卡顿非常严重,尽管检查了图片处理的线程是子线程,但是每秒钟大概处理6张预览图的时候还是依然卡顿,因此我排除了这个方法。
除了Camera2的方法以外,还有Camera的方法,而原理介绍中已经讲解了方法,如下:
三、获取一帧预览图像
Camera.setOneShotPreviewCallback(PreviewCallback cb)方法可以获取一帧预览图像,数据会返回到PreviewCallback 回调中的 onPreviewFrame(byte[] data,Camera camera)方法中,data即为帧数据。
由于我已经在Camera2测试了,因此我并没有直接使用这个方法来测试,而是参考了Zxing的二维码扫描这个库,因为这个二维码的处理逻辑其实是非常相似的,只是Zxing是解析二维码,而我们在藏红包的时候需要做的是处理奇数(单数)张图片与偶数(双数)张图片是否相似?
这里我是这样实现:通过拿到的Bitmap来加入一个集合(因为后面如果满足相似要求的话需要用到),接下来判断集合的长度 size 对2取余(就是除以2的余数)是否等于1,是的话,则是单数,否则的话就是双数。
这里比较简单,代码稍后一起贴出来。
接下来关于图片相似度方法,一般都是深度学习的算法,或者在知乎上面找到了一些建议,不过对于通过图片的简单像素做对比,基本上是不能做比较的,后面找到了一位大拿通过实现另一位大咖 阮一峰的文章,(相似图片搜索原理或者Android实现图片相似度),测试以后终于可以实现了。
首先,我先说一下我参考的是一个比较简单的ZXing demo ,(点击这里),通过代码分析,我只是简单的在解析的包里做的处理,代码如下:
包名:package com.breadykid.searchitem.scan.decode类:DecodeHandlerString lastHashValue,currentHashValue = null; /** * Decode the data within the viewfinder rectangle, and time how long it * took. For efficiency, reuse the same reader objects from one decode to * the next. * * @param data * The YUV preview frame. * @param width * The width of the preview frame. * @param height * The height of the preview frame. */ private void decode(byte[] data, int width, int height) { Size size = activity.getCameraManager().getPreviewSize(); Log.e(TAG,"decode"); // 这里需要将获取的data翻转一下,因为相机默认拿的的横屏的数据 byte[] rotatedData = new byte[data.length]; for (int y = 0; y < size.height; y++) { for (int x = 0; x < size.width; x++) rotatedData[x * size.height + size.height - y - 1] = data[x + y * size.width]; } for (int i = 0; i < rotatedData.length; i++) { } // 宽高也要调整 int tmp = size.width; size.width = size.height; size.height = tmp; //Bitmap的生成 YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null); ByteArrayOutputStream stream = new ByteArrayOutputStream(); image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream); Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size()); bmpData.add(bmp); LUtil.e("decodeData");// decodeData(bmp); //奇数 if(bmpData.size()%2 == 1){ lastHashValue = BitmapCompare.bitmapCompare(bmp); }else{//偶数 currentHashValue = BitmapCompare.bitmapCompare(bmp); //相似度 int diff = BitmapCompare.diff(lastHashValue,currentHashValue); Log.e(TAG,"相识度:"+diff); bmpData.clear(); currentHashValue = lastHashValue = null; if(diff == 0){ //相似度次数 bmpCount++; } } PlanarYUVLuminanceSource source = null; if(bmpCount==3){ //返回的数据 待加工 source = buildLuminanceSource(rotatedData, size.width, size.height); } Handler handler = activity.getHandler(); if (source != null) { // Don't log the barcode contents for security. if (handler != null) { Message message = Message.obtain(handler, R.id.decode_succeeded); Bundle bundle = new Bundle(); //返回的数据加工成Bitmap bundleThumbnail(source, bundle); message.setData(bundle); message.sendToTarget(); } } else { if (handler != null) { Message message = Message.obtain(handler, R.id.decode_failed); message.sendToTarget(); } } }/** * 图片相似度计算 * Created by 魏兴 on 2017/9/25. */public class BitmapCompare { /** * 获取图片哈希值 * @param bitmap * @return */ public static String bitmapCompare(Bitmap bitmap){ String result = null; bitmap = convertGreyImg(bitmap); int avg = getAvg(bitmap); String binary = getBinary(bitmap,avg); result = binaryString2hexString(binary); return result; } /** * 简化色彩 * @param img * @return */ public static Bitmap convertGreyImg(Bitmap img) { int width = img.getWidth(); //获取位图的宽 int height = img.getHeight(); //获取位图的高 int[] pixels = new int[width * height]; //通过位图的大小创建像素点数组 img.getPixels(pixels, 0, width, 0, 0, width, height); int alpha = 0xFF << 24; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int original = pixels[width * i + j]; int red = ((original & 0x00FF0000) >> 16); int green = ((original & 0x0000FF00) >> 8); int blue = (original & 0x000000FF); int grey = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11); grey = alpha | (grey << 16) | (grey << 8) | grey; pixels[width * i + j] = grey; } } Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); result.setPixels(pixels, 0, width, 0, 0, width, height); return result; } /** * 计算像素平均值 * @param img * @return */ public static int getAvg(Bitmap img) { int width = img.getWidth(); int height = img.getHeight(); int[] pixels = new int[width * height]; img.getPixels(pixels, 0, width, 0, 0, width, height); int avgPixel = 0; for (int pixel : pixels) { avgPixel += pixel; } return avgPixel / pixels.length; } /** * 计算像素灰度 * @param img * @param average * @return */ public static String getBinary(Bitmap img, int average) { StringBuilder sb = new StringBuilder(); int width = img.getWidth(); int height = img.getHeight(); int[] pixels = new int[width * height]; img.getPixels(pixels, 0, width, 0, 0, width, height); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int original = pixels[width * i + j]; if (original >= average) { pixels[width * i + j] = 1; } else { pixels[width * i + j] = 0; } sb.append(pixels[width * i + j]); } } return sb.toString(); } /** * 计算哈希值 * @param bString * @return */ public static String binaryString2hexString(String bString) { if (bString == null || bString.equals("") || bString.length() % 8 != 0) return null; StringBuilder sb = new StringBuilder(); int iTmp; for (int i = 0; i < bString.length(); i += 4) { iTmp = 0; for (int j = 0; j < 4; j++) { iTmp += Integer.parseInt(bString.substring(i + j, i + j + 1)) << (4 - j - 1); } sb.append(Integer.toHexString(iTmp)); } return sb.toString(); } /** * 比较哈希值 * @param s1 * @param s2 */ public static int diff(String s1, String s2) { char[] s1s = s1.toCharArray(); char[] s2s = s2.toCharArray(); int diffNum = 0; for (int i = 0; i<s1s.length; i++) { if (s1s[i] != s2s[i]) { diffNum++; } } return diffNum; } /** * bitmap转为base64 * @param bitmap * @return */ public static String bitmapToBase64(Bitmap bitmap) { String result = null; ByteArrayOutputStream baos = null; try { if (bitmap != null) { baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); baos.flush(); baos.close(); byte[] bitmapBytes = baos.toByteArray(); result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (baos != null) { baos.flush(); baos.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; } /** * base64转为bitmap * @param base64Data * @return */ public static Bitmap base64ToBitmap(String base64Data) { byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT); return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); }}
处理了上面的代码,现在我们可以像扫描二维码一样去调用CaptureActivity,当然返回的时候是就不要去获取解析的内容了,我们只要获取图片就好了。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == QUEST_AR_CODE){ if(resultCode ==RESULT_OK){ Bundle bundle = data.getExtras(); byte[] bmpData = bundle.getByteArray(DecodeThread.BARCODE_BITMAP); int width = bundle.getInt("width"); int height = bundle.getInt("height"); Bitmap moneyBmp = BitmapFactory.decodeByteArray(bmpData,0,bmpData.length);// File dirFile = FileUtil.createFileDir(this,"ARMoney");// imgpath = FileUtil.saveBitmap(dirFile,"temp.png",moneyBmp); //base64字符串 imgpath = BitmapCompare.bitmapToBase64(moneyBmp); if(imgpath==null){ Toast.makeText(this,"红包保存失败,请再次尝试",Toast.LENGTH_SHORT).show(); }else{ tvImg.setText(etMoney.getText()+"元"); tvImg.setVisibility(View.VISIBLE); etMoney.setVisibility(View.GONE); btnSend.setVisibility(View.GONE); tvUnit.setVisibility(View.GONE); } }else if(resultCode == RESULT_CANCELED){ LUtil.e("自动取消"); }else{ Toast.makeText(this,"AR红包隐藏失败",Toast.LENGTH_SHORT).show(); } } }
通过上面的代码,我们就实现了AR红包的藏,扫的话已经没有问题了,只是逻辑的实现而已。本来打算通过这个项目彻底了解一番Camera2的相关知识点,这点未达成;本来打算彻底学习图片的一些基础知识,这里也没有达成,只是简单的Ctrl+C 、Ctrl+V。这里意外学到的东西是关于scheme协议的使用、系统自带分享的使用(不能同时分享图片和文本),然后就是关于AppCompatActivity的全屏以及API 19 的沉浸式状态栏的实现。简单的说就是深度的东西没有学到,零碎的基础倒是一点一点的在积累。
参考文章:(文中均有链接)
AR红包Android端实现原理
Android实现图片相似度
ZXing简化
源码
正在优化中
- AR相机的实现
- AR 相机扫描效果实现
- 基于Unity3D的相机功能的实现(二)——AR相机
- Vuforia SDK---- AR开发vuforia 相机自动对焦代码实现
- Vuforia SDK---- AR开发vuforia 相机自动对焦代码实现
- 高通AR-关于切换相机跟踪图片的控制
- 相机的实现
- Android相机的实现
- AR--AR 小应用究竟是如何实现的
- AR乐园实例教程项目1---AR动物园(上)的实现
- AR乐园实例教程项目1---AR动物园(下)的实现
- Vuforia SDK---- AR开发vuforia 相机前后摄像头动态切换功能实现
- Vuforia SDK---- AR开发vuforia 相机前后摄像头动态切换功能实现
- 基于标记的AR的OpenCV实现
- 基于标记的AR的OpenCV实现
- Android实现自定义的相机
- AR实现透明视频的播放
- 用Unity3D实现简易的AR Demo
- Redis系列之复制(四)
- 个人项目DarkGold项目搭建
- 走出一小步
- [新手题]reverse_factorial(python)
- 怎样解题:写题解思考问题的原则
- AR相机的实现
- OpenCV实现Matlab的circshift、fftshift、ifftshift函数
- Android ORM数据库--ActiveAndroid的使用
- HDOJ2_Sum Problem
- java语言基础(97)——匿名内部类实现多线程
- js原生图片轮播
- 如何优化tomcat配置(从内存、并发、缓存)优化
- Gitlab配置ssh连接
- Guard