安卓OpenCV开发(三)竖屏检测

简介: 安卓OpenCV开发

前言

对于OpenCV的竖屏检测,网络有很多,多到百度排名前几页,都是一大堆,但为什么要写这个文章,因为他们的文章,大部分都是有问题,或者是不可用的,以下为他们的实现方法:
(1)随便改改canvas绘制方向,并无其他操作
(2)使用WindowManager检测屏幕旋转方向,修改canvas绘制方式
对于以上两种做法,实际是欠佳的,改的是canvas,不是数据源,识别过程还得横屏才能识别,难道写这些文章的人都没发现?

方法

首先,官方的Demo,跑的是横屏逻辑。而横屏和竖屏最大的区别就是在于角度和长宽比不一样。核心原理,在于宽高的转换,方向的改变。

怎样改?
首先,先对JavaCameraView代码进行修改,核心改动如下:

private class JavaCameraFrame implements CvCameraViewFrame {
        @Override
        public Mat gray() {
            //返回Mat里的选定区域,这跟Yuv420sp格式紧密相关
            //return mYuvFrameData.submat(0, mHeight, 0, mWidth);
            //#Modified step3.1
            Core.rotate(mYuvFrameData.submat(0, mHeight, 0, mWidth),
                    portrait_gray,Core.ROTATE_90_CLOCKWISE);
            return portrait_gray;
        }

        @Override
        public Mat rgba() {
            if (mPreviewFormat == ImageFormat.NV21)
                Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGBA_NV21, 4);
            else if (mPreviewFormat == ImageFormat.YV12)
                Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGB_I420, 4);  // COLOR_YUV2RGBA_YV12 produces inverted colors
            else
                throw new IllegalArgumentException("Preview Format can be NV21 or YV12");

            //#Modified step3.2
            Core.rotate(mRgba, portrait_rgba,Core.ROTATE_90_CLOCKWISE);

            return portrait_rgba;
        }

        public JavaCameraFrame(Mat Yuv420sp, int width, int height) {
            super();
            mWidth = width;
            mHeight = height;
            //#Modified
            portrait_mHeight=mWidth;
            portrait_mWidth=mHeight;
            portrait_gray=new Mat(portrait_mHeight,portrait_mWidth,CvType.CV_8UC1);
            portrait_rgba=new Mat(portrait_mHeight,portrait_mWidth,CvType.CV_8UC4);
            mYuvFrameData = Yuv420sp;
            mRgba = new Mat();
        }

        public void release() {
            mRgba.release();
        }

        private Mat mYuvFrameData;
        private Mat mRgba;
        private int mWidth;
        private int mHeight;
        //#Modified
        private int portrait_mHeight;
        private int portrait_mWidth;
        private Mat portrait_gray;
        private Mat portrait_rgba;
    }

其实原理,就是通过Core.rotate()方法进行旋转。

第二个,修改CameraBridgeViewBase的代码,核心代码如下:

/**
     * This helper method can be called by subclasses to select camera preview size.
     * It goes over the list of the supported preview sizes and selects the maximum one which
     * fits both values set via setMaxFrameSize() and surface frame allocated for this view
     *
     * @param supportedSizes
     * @param surfaceWidth
     * @param surfaceHeight
     * @return optimal frame size
     */
    protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
        //选择一个相机frame大小
        int calcWidth = 0;
        int calcHeight = 0;

        //允许的最大width和height
        //#Modified step4
        //相机Frame的mMaxWidth应该与surface的surfaceHeight比
        //相机Frame的mMaxHeight应该与surface的surfaceWidth比
        //int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
        //int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
        int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceHeight) ? mMaxWidth : surfaceHeight;
        int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceWidth) ? mMaxHeight : surfaceWidth;

        for (Object size : supportedSizes) {
            int width = accessor.getWidth(size);
            int height = accessor.getHeight(size);

            //在允许的范围内选择最大的size
            //client是可通过设置小的mMaxWidth,mMaxHeight来选择低分辨率frame的
            if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
                if (width >= calcWidth && height >= calcHeight) {
                    calcWidth = (int) width;
                    calcHeight = (int) height;
                }
            }
        }

        return new Size(calcWidth, calcHeight);
    }
/**
     * This method shall be called by the subclasses when they have valid
     * object and want it to be delivered to external client (via callback) and
     * then displayed on the screen.
     *
     * @param frame - the current frame to be delivered
     */
    protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
        Mat modified;

        if (mListener != null) {
            //CvCameraViewListener2 mListener是client指定的
            //这里调用客户重载的接口方法且接收返回值
            //这里都是在数据处理线程里执行的
            modified = mListener.onCameraFrame(frame);
        } else {
            //若client没指定CvCameraViewListener2 mListener即client不准备处理preview数据
            //则modified设置为
            //onPreviewFrame传回的数据转换成的rgba Mat
            modified = frame.rgba();
        }

        //Log Mat的大小和Bitmap的大小
        Log.d("FunnyAR", "mScale: " + mScale + " modified.rows: " + modified.rows()
                + " modified.cols: " + modified.cols() + " mCacheBitmap.getWidth(): " +
                mCacheBitmap.getWidth() + " mCacheBitmap.getHeight() " +
                mCacheBitmap.getHeight());

        //标志modified转Bitmap是否成功
        boolean bmpValid = true;
        //若确实有modified则将其转为Bitmap
        if (modified != null) {
            try {
                Utils.matToBitmap(modified, mCacheBitmap);
            } catch (Exception e) {
                Log.e(TAG, "Mat type: " + modified);
                Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
                Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
                bmpValid = false;
            }
        }
        //转换成功通过画布画到surface里
        if (bmpValid && mCacheBitmap != null) {
            Canvas canvas = getHolder().lockCanvas();
            if (canvas != null) {
                canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
                if (BuildConfig.DEBUG)
                    Log.d(TAG, "mStretch value: " + mScale);

                if (mScale != 0) {
                    canvas.drawBitmap(mCacheBitmap, new Rect(0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
                            new Rect((int) ((canvas.getWidth() - mScale * mCacheBitmap.getWidth()) / 2),
                                    (int) ((canvas.getHeight() - mScale * mCacheBitmap.getHeight()) / 2),
                                    (int) ((canvas.getWidth() - mScale * mCacheBitmap.getWidth()) / 2 + mScale * mCacheBitmap.getWidth()),
                                    (int) ((canvas.getHeight() - mScale * mCacheBitmap.getHeight()) / 2 + mScale * mCacheBitmap.getHeight())), null);
                } else {
                    canvas.drawBitmap(mCacheBitmap, new Rect(0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
                            new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
                                    (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
                                    (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
                                    (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
                }

                if (mFpsMeter != null) {
                    mFpsMeter.measure();
                    mFpsMeter.draw(canvas, 20, 30);
                }
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

通过修改获取帧大小和帧分发的函数,即可。具体原理就是宽高互换。

而对于前置摄像头的方向,这里不再啰嗦了,原理一样。

修改文件源码:链接: https://pan.baidu.com/s/1OJs8IgV0TD6_Hq6nU1g9Xg
提取码:tr4i
复制这段内容后打开百度网盘手机App,操作更方便哦

that's all-------------------------------------------------------------------------------------------

目录
相关文章
|
6天前
|
Android开发 开发者 Kotlin
探索安卓开发中的新特性
【9月更文挑战第14天】本文将引导你深入理解安卓开发领域的一些最新特性,并为你提供实用的代码示例。无论你是初学者还是经验丰富的开发者,这篇文章都会给你带来新的启示和灵感。让我们一起探索吧!
|
2天前
|
Java Linux Android开发
深入理解Android开发:从基础到高级
【9月更文挑战第17天】本文将深入探讨Android开发的各个方面,包括应用开发、操作系统等。我们将通过代码示例来展示如何创建一个简单的Android应用,并解释其背后的原理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和启示。
|
9天前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
5天前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
14 3
|
8天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
10天前
|
开发框架 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的指南
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文将深入浅出地对比这两大操作系统的开发环境、工具和用户体验设计,揭示它们在编程语言、开发工具以及市场定位上的根本差异。我们将从开发者的视角出发,逐步剖析如何根据项目需求和目标受众选择适合的平台,同时探讨跨平台开发框架的利与弊,为那些立志于打造下一个热门应用的开发者提供一份实用的指南。
24 5
|
10天前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术与科学
在移动应用开发的广阔天地中,安卓与iOS两大平台如同东西方哲学的碰撞,既有共通之处又各具特色。本文将深入探讨这两个平台的设计理念、开发工具和市场定位,旨在为开发者提供一份简明扼要的指南,帮助他们在这场技术与商业的博弈中找到自己的道路。通过比较分析,我们将揭示每个平台的优势与局限,以及它们如何影响应用的性能、用户体验和市场接受度。无论你是初涉江湖的新手,还是经验丰富的老手,这篇文章都将为你的选择提供新的视角和思考。
24 5
|
10天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
33 6
|
8天前
|
搜索推荐 Android开发 UED
安卓开发中的自定义视图:打造个性化用户界面
【9月更文挑战第11天】在安卓应用开发领域,自定义视图是实现独特用户体验的基石。本文将引导你通过一个简单的自定义视图示例,探索如何从零开始创建并应用自定义组件,以增强你的应用界面。我们将一起学习如何扩展View类,重写onDraw方法,处理触摸事件,并最终在我们的安卓项目中使用这个自定义视图。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供清晰的步骤和实用的技巧,帮助你提升用户界面设计的能力。
|
10天前
|
开发工具 Android开发 Swift
探索安卓与iOS开发的差异:从新手到专家的旅程
在数字时代的浪潮中,移动应用开发已成为连接世界的桥梁。本文将深入探讨安卓与iOS这两大主流平台的开发差异,带领读者从零基础出发,逐步了解各自的特点、开发环境、编程语言及市场策略。无论你是梦想成为移动应用开发者的初学者,还是希望扩展技能边界的资深开发者,这篇文章都将为你提供宝贵的见解和实用的建议。