跳到内容 跳到主导航 跳到页脚

摄像头

机器人的摄像头有的是单摄,有的是双摄;不同产品线摄像头使用不同,详见下面文档。

豹小秘1

单摄,可通过Android 标准 API 打开

豹小秘2和豹小秘mini

双摄,前置摄像头可以使用 Android 标准 API 打开,也可以使用共享流获取相机预览数据(通过 Android 标准 API 打开前置摄像头,会造成机器人视觉算法暂时无法使用,释放后,一定时间后会恢复视觉算法能力)。后置摄像头只能使用Android 标准 API 打开。

招财豹和招财豹Pro

单摄,前置摄像头可以使用 Android 标准 API 打开,也可以使用共享流获取相机预览数据(通过 Android 标准 API 打开前置摄像头,会造成机器人视觉算法暂时无法使用,释放后,一定时间后会恢复视觉算法能力)。

注意

使用Android的API打开相机,第一次授权APP使用摄像头时,APP可能会崩溃,但之后使用就没有限制了。如果遇到授权摄像头崩溃,重新开启即可,之后可正常使用摄像头。

横屏机器人注意:通过Android标准方式启动之后方向是选择90°的,这是因为机器人没有陀螺仪,程序中默认是竖屏摄像头,但机器人实际是横屏设备。解决方案很简单,通过Android API(或其它各家视频sdk的API)设置摄像头旋转-90°或摄像头旋转270°即可纠正。

以下Demo为Android相机API使用,仅供参考,如不满足,请自行根据需求上网查询

Camera1的Demo

Camera2的Demo

摄像头数据流共享

因为机器人视觉能力VisionSDK需要使用摄像头,如果摄像头被二次开发的程序占用,会导致机器人人脸检测、人脸识别不可用。如果想既使用摄像头,又不影响机器人视觉功能,需要使用共享数据流的方式获取VisionSDK中的摄像头数据。摄像头数据通过SurfaceShareApi获取。它主要有三个关键方法:

  1. startRequest() : 开启共享数据流
  2. onImageAvailable(ImageReader reader) :这是一个回调,所有共享流里的图像数据从这里出来
  3. stopPushStream() :关闭共享数据流

共享流方式获取图像数据内存和CPU开销都比较大,在不使用的时候一定记得关闭,避免引起OOM。共享数据流取到的图片是YUV格式的,要渲染到surfaceview需要转换为bitmap。

具体参加下面代码:

package com.ainirobot.videocall.control.sufaceshareengine;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.Surface;

import com.ainirobot.coreservice.client.surfaceshare.SurfaceShareApi;
import com.ainirobot.coreservice.client.surfaceshare.SurfaceShareBean;
import com.ainirobot.coreservice.client.surfaceshare.SurfaceShareError;
import com.ainirobot.coreservice.client.surfaceshare.SurfaceShareListener;
import com.ainirobot.videocall.tools.ImageUtils;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * 通过Vision SDK获取视频数据
 */
public class SurfaceShareDataEngine {

    private static final String TAG = "SurfaceShareDataEngine";
    public static final int IMAGE_DATA_FOR_LIVEME = 0;
    public static final int IMAGE_DATA_FOR_LOCAL_PREVIEW = 1;

    public static final int VISION_IMAGE_WIDTH = 640;
    public static final int VISION_IMAGE_HEIGHT = 480;
    private static final int MAX_CACHE_IMAGES = 4; //maxImages 保持和VisionSdk一致

    private MessageCallBack mCallBack;
    private ImageReader mImageReader;

    private Handler mHandler;
    private HandlerThread mBackgroundThread;
    private SurfaceShareBean mSurfaceShareBean;
    private int mCurrentError = 0;
    private long log_interval = 0;
    private MyHandler mImageHandler;
    private HandlerThread mImageHandleThread;
    private Surface mRemoteSurface;

    private static class InstanceHolder {
        public static final SurfaceShareDataEngine instance = new SurfaceShareDataEngine();
    }

    public static SurfaceShareDataEngine getInstance() {
        return InstanceHolder.instance;
    }

    private SurfaceShareDataEngine() {}

    public void setMessageCallBack(MessageCallBack callBack){
        Log.i(TAG, "set message callBack: " + callBack);
        this.mCallBack = callBack;
    }

    private boolean initSurfaceResource() {
        Log.d(TAG, "initSurfaceResource ..");
        startBackgroundThread();
        boolean init = initImageSurface();
        if (!init) {
            Log.e(TAG, "Remote Surface instance has been used");
            return false;
        }
        startHandleImageThread();
        return true;
    }

    private void startHandleImageThread() {
        mImageHandleThread = new HandlerThread("ImageHandleThread");
        mImageHandleThread.start();
        mImageHandler = new MyHandler(mImageHandleThread.getLooper());
    }

    public void startRequest() {
        initSurfaceResource();
        if (mSurfaceShareBean == null) {
            mSurfaceShareBean = new SurfaceShareBean();
            mSurfaceShareBean.setName("VideoCall");
        }

        mRemoteSurface = mImageReader.getSurface();
        Log.d(TAG, "startRequest getSurface:" + mRemoteSurface);
        SurfaceShareApi.getInstance().requestImageFrame(mRemoteSurface, mSurfaceShareBean, new SurfaceShareListener() {

            @Override
            public void onError(int error, String message) {
                super.onError(error, message);
                Log.d(TAG, "requestImageFrame onError error :" + error);
                mCurrentError = error;
                if (mCallBack != null) {
                    mCallBack.onError(error);
                }
            }

            @Override
            public void onStatusUpdate(int status, String message) {
                super.onStatusUpdate(status, message);
                Log.d(TAG, "requestImageFrame onStatusUpdate status:" + status);
            }
        });
    }

    private boolean initImageSurface() {
        if (mImageReader != null) {
            Log.d(TAG, "initImageSurface has an ImageReader instance already!");
            return false;
        }
        mImageReader = ImageReader.newInstance(VISION_IMAGE_WIDTH , VISION_IMAGE_HEIGHT, ImageFormat.YUV_420_888, MAX_CACHE_IMAGES);
        mImageReader.setOnImageAvailableListener(new MyOnImageAvailableListener(), mHandler);
        return true;
    }

    private void startBackgroundThread() {
        if (mBackgroundThread != null && mHandler != null) {
            Log.d(TAG, "startBackgroundThread has already start a mBackgroundThread");
            return;
        }
        mBackgroundThread = new HandlerThread("ExternalBackground");
        mBackgroundThread.start();
        mHandler = new Handler(mBackgroundThread.getLooper());
    }

    private class MyOnImageAvailableListener implements ImageReader.OnImageAvailableListener {

        @Override
        public void onImageAvailable(ImageReader reader) {
            long startTime = System.currentTimeMillis();
            Image image = null;
            try {
                image = reader.acquireLatestImage();
                if (image == null){
                    Log.d(TAG, "onImageAvailable image is null");
                    return;
                }
                if (mBackgroundThread == null){
                    Log.d(TAG, "onImageAvailable mBackgroundThread is null");
                    return;
                }
 //以下是猎豹的业务逻辑,此处可以改为其它业务逻辑。注意处理完成后一定要释放图片,否则极容易引起OOM崩溃
                //pushYuv(image);

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (log_interval % 60 == 0){
                    Log.d(TAG, "onImageAvailable push image time:"+(System.currentTimeMillis() - startTime)+"ms");
                }
                log_interval++;

                if (image != null) {
                    image.close();
                }
            }
        }
    }

    private class MyHandler extends Handler{

        MyHandler(Looper looper){
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg != null){
                Map<Integer, byte[]> data = (Map<Integer, byte[]>) msg.obj;
                if (mCallBack != null) {
                    mCallBack.onData(data);
                }
            }
        }
    }

    private void stopBackgroundThread() {
        if (mBackgroundThread == null) {
            Log.i(TAG, "the background thread is null");
            return;
        }

        Log.i(TAG, "stopBackgroundThread thread id:" + mBackgroundThread.getName() + "," + mBackgroundThread.getThreadId());
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join(50);
            mBackgroundThread = null;
            mHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void stopHandleThread() {
        if (mImageHandleThread == null) {
            Log.i(TAG, "the mImageHandleThread is null");
            return;
        }
        mImageHandleThread.quitSafely();
        try {
            mImageHandleThread.join(50);
            mImageHandleThread = null;
            mImageHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void closeImageReader() {
        Log.d(TAG, "closeImageReader == ");
        if (mImageReader != null) {
            mImageReader.close();
            mImageReader = null;
        }
        Log.d(TAG, "closeImageReader mRemoteSurface release");
        if (mRemoteSurface != null){
            mRemoteSurface.release();
            mRemoteSurface = null;
        }
    }

    public void stopPushStream() {
        if (mCurrentError == SurfaceShareError.ERROR_SURFACE_SHARE_USED){
            Log.d(TAG, "stopPushStream ERROR_SURFACE_SHARE_USED .");
            return;
        }
        SurfaceShareApi.getInstance().abandonImageFrame(mSurfaceShareBean);
        stopBackgroundThread();
        stopHandleThread();
        closeImageReader();
        mSurfaceShareBean = null;
        mCurrentError = 0;
        log_interval = 0;
    }

    public interface MessageCallBack{
        void onData(Map<Integer, byte[]> bytes);
        void onError(int error);
    }
}

在猎户的机器人代码中的逻辑代码是这样处理YUV图像的:

    pushYuv(image);



    private void pushYuv(@NonNull Image image) {
        byte[] yuv = new byte[image.getHeight() * image.getWidth() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
        if (log_interval % 60 == 0){
            Log.d(TAG, "pushYuv w:" + image.getWidth() + ",h:" + image.getHeight() + ",planes.len:" + image.getPlanes().length+", log_interval:"+log_interval);
        }
        readImageIntoBuffer(image, yuv);

//        frameMirror(yuv, image.getWidth(), image.getHeight());

        if (mImageHandler == null){
            Log.d(TAG, "pushYuv mImageHandler is null");
            return;
        }

        byte[] localPreviewData = ImageUtils.generateNV21Data(image);

        Map<Integer, byte[]> dataMap = new HashMap<>();
        dataMap.put(IMAGE_DATA_FOR_LIVEME, yuv);
        dataMap.put(IMAGE_DATA_FOR_LOCAL_PREVIEW, localPreviewData);
        Message.obtain(mImageHandler,0, dataMap).sendToTarget();
    }


    public static void conver_argb_to_i420(byte[] i420, byte[] argb, int width, int height) {
        final int frameSize = width * height;

        int yIndex = 0;                   // Y start index
        int uIndex = frameSize;           // U statt index
        int vIndex = frameSize * 5 / 4; // V start index: w*h*5/4

        int a, R, G, B, Y, U, V;
        int index = 0;
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {
                a = (argb[index] & 0xff000000) >> 24; //  is not used obviously
                R = (argb[index] & 0xff0000) >> 16;
                G = (argb[index] & 0xff00) >> 8;
                B = (argb[index] & 0xff) >> 0;

                // well known RGB to YUV algorithm
                Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

                // I420(YUV420p) -> YYYYYYYY UU VV
                i420[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
                if (j % 2 == 0 && i % 2 == 0) {
                    i420[uIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
                    i420[vIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
                }
                index++;
            }
        }
    }

    private static void readImageIntoBuffer(Image image, byte[] data) {
        int width = image.getWidth();
        int height = image.getHeight();
        Image.Plane[] planes = image.getPlanes();
        if (planes == null) {
            Log.i(TAG, "get image planes is null");
            return;
        }
        int offset = 0;
        int planeLen = planes.length;
        for (int plane = 0; plane < planeLen; plane++) {
            ByteBuffer buffer = planes[plane].getBuffer();
            int rowStride = planes[plane].getRowStride();
            int pixelStride = planes[plane].getPixelStride();
            int planeWidth = plane == 0 ? width : width / 2;
            int planeHeight = plane == 0 ? height : height / 2;
            if ((pixelStride == 1) && (rowStride == planeWidth)) {
                buffer.get(data, offset, planeWidth * planeHeight);
                offset += planeWidth * planeHeight;
            } else {
                byte[] rowData = new byte[rowStride];
                for (int row = 0; row < planeHeight - 1; row++) {
                    buffer.get(rowData, 0, rowStride);
                    for (int col = 0; col < planeWidth; col++) {
                        data[(offset++)] = rowData[(col * pixelStride)];
                    }
                }
                buffer.get(rowData, 0, Math.min(rowStride, buffer.remaining()));
                for (int col = 0; col < planeWidth; col++) {
                    data[(offset++)] = rowData[(col * pixelStride)];
                }
            }
        }
    }

    public static byte[] frameMirror(byte[] data, int width, int height) {
        byte tempData;
        for (int i = 0; i < height * 3 / 2; i++) {
            for (int j = 0; j < width / 2; j++) {
                tempData = data[i * width + j];
                data[i * width + j] = data[(i + 1) * width - 1 - j];
                data[(i + 1) * width - 1 - j] = tempData;
            }

        }
        return data;
    }

    public static byte[] readRgbaImageToBuffer(Image imageFrame) {

        int w = imageFrame.getWidth();
        int h = imageFrame.getHeight();
        Image.Plane[] planes = imageFrame.getPlanes();
        if (planes == null) {
            Log.i(TAG, "get image planes is null");
            return null;
        }
        ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * w;
        Log.d(TAG, "readImageIntoBuffer: rowStride:" + rowStride + ",pixelStride:" + pixelStride + ",rowPadding:" + rowPadding);
        Bitmap bitmap = Bitmap.createBitmap(w + rowPadding / pixelStride, h, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(buffer);


        byte[] pixels = new byte[w * h * 4]; // Allocate for RGB

        int k = 0;

        for (int x = 0; x < h; x++) {
            for (int y = 0; y < w; y++) {
                int color = bitmap.getPixel(y, x);
                // rgba
                pixels[k * 4 + 0] = (byte) Color.red(color);
                pixels[k * 4 + 1] = (byte) Color.green(color);
                pixels[k * 4 + 2] = (byte) Color.blue(color);
                pixels[k * 4 + 3] = (byte) Color.alpha(color);
                k++;
            }
        }

        return pixels;
    }

SurfaceShareDataEngine.java

这篇文章是否有帮助?

0