AR相机的实现
Posted Vicent_9920
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AR相机的实现相关的知识,希望对你有一定的参考价值。
首先需要声明的是,我并不是原创每个概念,而是在前人的基础上做了一个实现,原理如图:
上面的图片来自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
类:DecodeHandler
String 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相机的实现的主要内容,如果未能解决你的问题,请参考以下文章
N个同学排队,1,2,1,2报数,单数退出,双数一排,1,2,1,2报数,单数退出,最后剩下的人原站哪?