RK3399上的Tengine实践笔记

目录

0. 目的

1. 搭建环境

2. TengineKit Demo项目结构

3. 问题及解决


0. 目的

对RK3399单板的HDMI_IN输入(模拟Camera)进行人脸识别并对识别结果进行模糊化处理。

1. 搭建环境

1.1 安装Android Studio

我下的是这个android-studio-ide-201.6953283-linux.tar.gz,下载地址找不到了。

1.2 下载TengineKit

参考文章链接:

知乎:用开源212点人脸关键点实现Android人脸实时打码

极术社区:《用开源212点人脸关键点实现Android人脸实时打码》

Github源码链接

git clone https://github.com/OAID/TengineKit.git

1.3 设置AndroidStudio环境

·  第一个比较坑的就是gradle的同步地址要更新以下,可参考

· 第二个就是设置SDK,我用的是安卓7.1,File --- settings --- Appearance...  --- Android SDK设置如下:

· build.gradle设置如下:(按说targetSdkVersion应该是25才对,但不知道为什么28也可以,有空再搞清楚这几个SdkVersion 的关系)

2. TengineKit Demo项目结构

整个项目文件结果如下图。水平有限说不出来个啥。

  • CameraEngine.java就是根据前后摄像头的配置设置了以下相机的旋转角度,但是因为RK3399的HDMI_IN比较特殊,虽然camera id为0(facing back)但是感觉这个facing back传递不到后面的配置中。
  • CameraConnectionFragment.java里好像最重要的就是下面这个方法了( protected static Size chooseOptimalSize),作用就是选一个可以根据设置的预览分辨率大小最优的长宽比。
  • 改的最多的是LegacyCameraConnectionFragment和ClassifierActivity两个类,前者功能主要是“完成Camera所支持的预览分辨率的读取、选取和配置”、“完成Camera的preview方向、申请preview图像的回调buffer”等功能。后者主要是调用TengineKit的jar(aar)包、根据人脸数据对图像进行模糊处理等。
  • utils下的是图像类的一些处理方法,如高斯模糊等。

3. 问题及解决

个人猜测:TengineKit在设计之初是为了手机(竖屏)使用的,因此默认build出来的版本,输出的图像是顺时针旋转了90°的,如下:

RK3399终端的屏幕是横屏(按网上很多同学的说法就是0°,但不知为何我把所有的旋转都去掉,识别率几乎等于0,我猜测模型训练的时候跟相机/人脸图片的角度有关系)

我们需要的是一个横屏的画面。改所有的rotation设置貌似都无效,那就只能老老实实地沉下心来学习别人的代码了。此处略去一段光阴。。。。。。。

最终有以下几个确认点:

1. TengineKit的输入图像有角度限制,否则识别率很低。对于0°横置的后置Camera设备,TengineKit的输出(主要说的是faceLandmarks.get(i).getBoundingBox()方法返回的Rect对象的位置)是“水平镜像后、顺时针转90°、并作宽高比调整后”的结果

2. 对Bitmap的后处理需要根据TengineKit的输出行为做相应的改动。如我们拿到TengineKit的输出(faceLandmarks.get(i).getBoundingBox()方法返回的Rect对象)后,需要逆时针转90°(与宽高比转换一起完成),然后再做一个水平mirror的处理才能得到我们正确的结果。

根据这两个发现,一共修改了LegacyCameraConnectionFragment.java和ClassifierActivity.java两个文件。代码如下:

  • LegacyCameraConnectionFragment.java
public void onSurfaceTextureAvailable(final SurfaceTexture texture, final int width, final int height) {
......
......
// camera.setDisplayOrientation(90);    // original
   camera.setDisplayOrientation(0);     // updated
......
......
// camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.height, s.width)]); // original
// textureView.setAspectRatio(s.height, s.width); // original
camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.width, s.height)]);// updated
textureView.setAspectRatio(s.width, s.height);  //updated

            camera.startPreview();
        }
  • ClassifierActivity.java
 public void drawCallback(final Canvas canvas) {
    if(testBitmap != null){
        canvas.drawBitmap(testBitmap, 0,0, circlePaint);
        }
    if(faceLandmarks != null){
        for (int i = 0; i < faceLandmarks.size(); i++) {
            Rect r = Transform_anticlock_90deg(faceLandmarks.get(i).getBoundingBox(), outputWidth, outputHeight);    // updated
//          Rect r = faceLandmarks.get(i).getBoundingBox(); // original
                        canvas.drawRect(r, circlePaint);
                        canvas.drawBitmap(testFaceBitmaps.get(i), r.left, r.top, circlePaint);
            }
        }
 }


protected void processImage(byte[] data, int previewWidth, int previewHeight, int outputWidth, int outputHeight) {

.......
......
    if(testBitmap != null){
        testBitmap.recycle();
    }
    testBitmap = Face.Image.convertCameraYUVData(
            data,
            previewWidth, previewHeight,
            outputWidth, outputHeight,
//          - 90,     // original
//          true);    // original
            0,          // updated
            false);     // updated


    
    Rect tmpRect = new Rect(0,1,0,1);  // added,临时变量,可不要。
    
    if(testBitmap != null && faceDetect.getFaceCount() > 0){
        if(faceLandmarks != null){
            for (int i = 0; i < faceLandmarks.size(); i++) {
                tmpRect = Transform_anticlock_90deg(faceLandmarks.get(i).getBoundingBox(),outputWidth,outputHeight); //added
                    Bitmap face = BitmapUtils.getDstArea(testBitmap, tmpRect); //added
//               Bitmap face = BitmapUtils.getDstArea(testBitmap, faceLandmarks.get(i).getBoundingBox());
                 face = BitmapUtils.blurByGauss(face, 50);
                 testFaceBitmaps.add(face);
            }
        }
    }
......
......
}


// 以下为新增代码。坐标原点(0,0)在左上角,显示区域在第四象限
    public Rect Transform_anticlock_90deg(Rect inRect, int view_width, int view_height)  // 逆时针转90°并水平镜像
    {
        Rect outRect = new Rect(0,10,0,10);
        final float h_to_w_ratio = (float)view_height/(float)view_width;
        final int x0,y0,x1,y1,newX0,newY0,newX1,newY1;
        x0 = inRect.left;    // 实际不需要x0
        y0 = inRect.top;
        x1 = inRect.left + inRect.width();
        y1 = inRect.top + inRect.height();

        //计算逆时针转90°后的坐标(含宽高比转换)
        newX0 = (int)((float)y0 / h_to_w_ratio);
        newY0 = (int)((float)(view_width - x1) * h_to_w_ratio);
        newX1 = (int)((float)y1 / h_to_w_ratio);
        newY1 = (int)((float)inRect.width() * h_to_w_ratio) + newY0;
        //生成旋转后的Rect
        outRect.set(newX0,newY0,newX1,newY1);
        //水平镜像
        outRect.set(RectMirror(outRect,view_width,view_height,true));
        
        return outRect;
    }

    private Rect RectMirror(Rect inRect, int view_width, int view_height, boolean isHorizontal)    // 镜像方法
    {
        Rect outRect = new Rect();
        
        if (isHorizontal == true) {    //水平镜像
            outRect.set(view_width - inRect.right, inRect.top, view_width - inRect.left, inRect.bottom);
        }
        else {                        //垂直镜像
            outRect.set(inRect.left, view_height-inRect.bottom, inRect.right, view_height-inRect.top);
        }
        return outRect;
    }
  • 另外修改显示的画面大小的地方也在ClassfierActivity.java里
protected Size getDesiredPreviewFrameSize() {
        return new Size(1920, 1080);  //我的片源是1080p的,如果改小了好像会显示不全,还是对Camera玩得不熟。
    }
  • 旋转以后的图像就正确了:

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值