Android Camera 预览尺寸的选取与旋转角度的设定

Camera 在音视频的世界中, 就像我们的眼睛一样, 是负责图像采集的入口, 要想实现一款高可用的 Camera, 我认为需要关注以下几个方面

  • 相机的预览
  • 相机的拍摄
  • 相机的录制

OpenGL ES 主要用于图像的渲染, 因此这里主要聚焦于相机的预览, 想要让相机的数据呈现到我们屏幕上, 主要有如下几个步骤

  • 相机预览帧尺寸的选取
  • 相机预览帧旋转角度的设定
  • 使用 OpenGL ES 渲染数据

可以看到每一步想要做好都是挺有挑战性的, 本篇主要分析 相机预览帧尺寸的选取相机预览帧旋转角度的设定 这两个问题

若是使用 JetPack 组件 CameraX 的话, 基本上无需考虑这两个问题, 不过为了解析其实现思路, 这里先以 Camera1 为例, 后续会给出 CameraX 的实现代码

一. 相机预览帧尺寸的选取

如何选取预览尺寸?

在刚开始接触自定义相机的时候, 这个问题一度困扰着我, 是因为面对这个问题我一直无法给予一个很好的解决方案, 之前我的方案是, 交由用户传入一个预览的尺寸, 然后设计一个算法从相机中找寻与这个尺寸最为接近的, 这个方案看似挺合理的, 写 demo 时可以使用, 但却存在着如下的隐患

  • 不同的手机支持的预览尺寸是不一样的, 尤其是在当下 3 摄 4 摄横行的手机市场上, 每个摄像头都有自己的特性, 如广角镜头、景深信息等, 指定一个合适的尺寸成本是非常高的
  • 若我们在竖直情况下指定了 1080*1440 的尺寸, 当我们手机横置时, 要想保证合理性, 则需要让 API 使用者自己监听屏幕的旋转, 然后再反转尺寸, 这种 API 使用成本过于高昂

因此让外界指定预览尺寸的方式有些不太合理, 于是我们把目光转移到大厂的系统相机上, One UI 的系统相机如下

One UI 的系统相机

可以发现, 它们仅提供的是尺寸的比例, 它的优势如下

  • 使用简单, 仅需要指定比例即可, 使用者无需考虑旋转的问题
    • 相机的尺寸比例一般为 4:3, 16:9, 其他比例是在此基础上裁剪出来的
  • 能够获取最佳预览效果, 同比例中选择最接近 View 宽高的, 可以避免相机输出的预览帧尺寸过高, 造成性能损耗, 引起预览卡顿

因此我们可以尝试采用外界指定比例, 在同比例中选取最接近预览控件尺寸的方式作为预览帧的采集的尺寸

若想实现上述的方式, 我们第一步要做的是对相机的预览尺寸按照比例进行归类, 方便从后续从中筛选出最合适的尺寸, 接下来我们便看看第一步的操作

一) 尺寸的归类

private final SizeMap mPreviewSizes = new SizeMap();

// 变量所有的预览尺寸, 按照比例分类
for (Camera.Size size : mCameraParams.getSupportedPreviewSizes()) {
    mPreviewSizes.add(new Size(size.width, size.height));
}

这里遍历了该 Camera 支持的所有预览尺寸, SizeMap.add 方法如下

class SizeMap {

    private final ArrayMap<AspectRatio, SortedSet<Size>> mRatios = new ArrayMap<>();

    /**
     * Add a new {@link Size} to this collection.
     *
     * @param size The size to add.
     * @return {@code true} if it is added, {@code false} if it already exists and is not added.
     */
    public boolean add(Size size) {
        for (AspectRatio ratio : mRatios.keySet()) {
            if (ratio.matches(size)) {
                final SortedSet<Size> sizes = mRatios.get(ratio);
                if (sizes.contains(size)) {
                    return false;
                } else {
                    sizes.add(size);
                    return true;
                }
            }
        }
        // None of the existing ratio matches the provided size; add a new key
        SortedSet<Size> sizes = new TreeSet<>();
        sizes.add(size);
        mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes);
        return true;
    }

}

通过这种方式, 我们的 mPreviewSizes 便按照比例完成分类了, 接下来看看那尺寸的选取

二) 尺寸的选取

// 1. 获取用户期望的比例的集合
SortedSet<Size> previewSizes = mPreviewSizes.sizes(aspectRatio);
if (previewSizes == null) {
    // 1.1 用户期望的比例不存在, 选取获取默认比例
    previewSizes = mPreviewSizes.sizes(chooseDefaultAspectRatio());
}
// 2. 选择最合适的预览帧尺寸
final Size previewSize = chooseOptimalPreviewSize(previewSizes);
// 参数注入
mCameraParams.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());

这里有两个需要注意的点

  • 当用户指定的比例不支持, 选取默认比例
  • 从比例对应的尺寸集合中, 选择最合适的预览帧尺寸

接下来逐一解析这两个算法

1. 选取默认比例
private AspectRatio chooseDefaultAspectRatio() {
    AspectRatio result = null;
    for (AspectRatio ratio : mPreviewSizes.ratios()) {
        result = ratio;
        if (AspectRatio.DEFAULT.equals(ratio)) {
            break;
        }
    }
    return result;
}

关于选取默认的比例代码比较简单, 即遍历预览比例集合, 判断是否存在默认比例, 若存在则返回默认比例, 反之则选取比例集合中的最后一个

  • 默认比例一般设置为 4:3 , 一般市面上所有相机都支持 4:3 的预览帧, 其支持的预览帧尺寸也较高
思考

当用户指定的比例不支持, 为什么不选取与用户设置最接近的比例呢?

为了回答这个问题, 先看看使用最接近比例算法最后选取的预览结果

 E/TAG: desire aspect is 1:1, closely is 11:9
 E/TAG: desired size is: [1080, 1080]; choose preview size is: [352x288]

可以看到从这个比例中选取的预览尺寸远低于 View 的尺寸, 从数据上看几乎会进行 3 倍的上采样, 即使上采样算法再优, 也难以保证预览的清晰度

我们无法控制用户心仪的比例, 因此当用户指定的比例不存在时, 采用指定的默认比例是能够保证预览清晰度的比较好的方式, 接下来看看如何从比例中选择最合适的预览尺寸

2. 选择最合适的预览尺寸
private Size chooseOptimalPreviewSize(SortedSet<Size> sizes) {
    // 1. 确定期望的宽高
    int desiredWidth;
    int desiredHeight;
    // 横屏状态, 直接使用 view 的宽高
    if (isLandscape(screenOrientationDegrees)) {
        desiredWidth = viewWidth;
        desiredHeight = viewHeight;
    }
    // 竖屏状态, 反转宽高
    else {
        desiredWidth = viewHeight;
        desiredHeight = viewWidth;
    }
    // 2. 查找最合适的预览尺寸
    Size result = null;
    for (Size size : sizes) {// sizes 已按照从小到大排序
        result = size;
        // 选取宽高都比期望稍大的
        if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
            return size;
        }
    }
    return result;
}

在确定期望的宽高的过程中, 竖屏状态下会有一个宽高翻转的操作

  • 这是因为相机的尺寸总是 1920:1080, 1280:720 这种样式的, 这一点从数码相机上可以体现, 他们的取景器一般都是宽大于高的, 这已经成为了行业的标准, 我们手机上的相机是从数码相机上演变过来的, 因此也继承了这个标准

尺寸查找的主要的思路是选取比期望宽高稍大的预览尺寸

  • 控件能够展示的像素个数是固定的, 即使选取的采集尺寸高于控件的尺寸, 但是还是会降采样为控件的像素数, 所以预览时选取与展示 View 宽高相似的尺寸即可

三) 验证选取结果

通过上面的选取方式, 验证的结果如下

// 期望比例 4:3
E/TAG: desired size is: [1440, 1080]; choose preview size is: [1600x1200]
 
// 期望比例 16:19
E/TAG: desired size is: [1920, 1080]; choose preview size is: [1920x1080]

// 期望比例 1:1
E/TAG: desired size is: [1440, 1080]; choose preview size is: [1600x1200]

可以看到最终选择的结果, 是符合我们上面算法预期的

  • 当然要想实现 P30 拍月亮的效果, 这种方式就不行了, 是需要多个镜头配合工作的, 笔者一时间觉得有些茫然

好的, 预览帧采集的尺寸已经确定好了, 接下来看看预览帧旋转角度的设定

二. 旋转角度的设定

想要正确的实现预览效果, 设定相机预览帧的旋转角度是必须要面对的问题, 想要解决这个问题, 我们需要先了解一下相机 sensor 的坐标系

一) Sensor 取景坐标系

以手机屏幕为参考系, 相机 Sensor 取景坐标系的指向与其安装的方位有关, 以后置相机为例, 其相机传感器的坐标系如下所示

相机 sensor 坐标系

可以看到, 一般相机的 sensor 坐标系如上图所示, 与手机逆时针旋转 90 度后的屏幕坐标系重合, 接下来根据这个坐标看看在手机横竖屏时, 图像的呈现效果

1. 横屏

横屏状态下的映射

可以看到横屏(逆时针 90°)状态下, Sensor 和屏幕的坐标系重合, 图像可以直接映射

2. 竖屏

竖屏状态下的映射

可以看到竖屏状态下的映射就出现问题了, 我们可以很自然的想到顺时针旋转 90° 来解决

了解了 sensor 坐标系和屏幕坐标系之间的映射, 接下来看看如何撰写相关代码

二) 旋转角度的确定

关于修改相机旋转角度的方法 Camera.setDisplayOrientation 定义如下

public class Camera {
    
    /**
     * ......
     * <pre>
     * public static void setCameraDisplayOrientation(Activity activity,
     *         int cameraId, android.hardware.Camera camera) {
     *     android.hardware.Camera.CameraInfo info =
     *             new android.hardware.Camera.CameraInfo();
     *     android.hardware.Camera.getCameraInfo(cameraId, info);
     *     int rotation = activity.getWindowManager().getDefaultDisplay()
     *             .getRotation();
     *     int degrees = 0;
     *     switch (rotation) {
     *         case Surface.ROTATION_0: degrees = 0; break;
     *         case Surface.ROTATION_90: degrees = 90; break;
     *         case Surface.ROTATION_180: degrees = 180; break;
     *         case Surface.ROTATION_270: degrees = 270; break;
     *     }
     *
     *     int result;
     *     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
     *         result = (info.orientation + degrees) % 360;
     *         result = (360 - result) % 360;  // compensate the mirror
     *     } else {  // back-facing
     *         result = (info.orientation - degrees + 360) % 360;
     *     }
     *     camera.setDisplayOrientation(result);
     * }
     * </pre>
     * ......
     */
    public native final void setDisplayOrientation(int degrees);
    
}

我们只需要将旋转角度计算好, 传入这个方法便可以让 Camera 输出符合屏幕预览的图像了, 关于这个旋转角度的计算, 其注释上有明确的代码示例, 其主要思想如下

  • 获取当前手机逆时针旋转的角度
  • 对前后摄像头分开处理

可以看到在计算前后摄像头旋转角度时除了用到屏幕的旋转角度, 还用到了 CameraInfo.orientation 这个变量, 接下来我们逐个分析

1. CameraInfo.orientation

关于 CameraInfo.orientation 其定义如下

public class Camera {
    
     public static class CameraInfo {
        /**
         * <p>The orientation of the camera image. The value is the angle that the
         * camera image needs to be rotated clockwise so it shows correctly on
         * the display in its natural orientation. It should be 0, 90, 180, or 270.</p>
         *
         * <p>For example, suppose a device has a naturally tall screen. The
         * back-facing camera sensor is mounted in landscape. You are looking at
         * the screen. If the top side of the camera sensor is aligned with the
         * right edge of the screen in natural orientation, the value should be
         * 90. If the top side of a front-facing camera sensor is aligned with
         * the right of the screen, the value should be 270.</p>
         *
         * .......
         */
        public int orientation;
    }
}

注释的译文为: orientation 表示相机图像的方向。它的值是相机图像顺时针旋转到设备自然方向一致时的图像,它可能是 0、90、180、270 四种。对于竖屏应用来说,后置相机传感器是横屏安装的,当你面向屏幕时,如果后置相机传感器顶边和设备自然方向的右边是平行的,那么后置相机的 orientation 是 90。如果是前置相机传感器顶边和设备自然方向的右边是平行的,则前置相机的 orientation 是 270

注释看起来可能比较抽象, 它的意思是指屏幕竖置时, 传感器采集的图像想要以符合人眼观感的样式显示到屏幕上, 所需要顺时针旋转的角度, 我们通过流程图来加深一下对其的理解

CameraInfo.orientation 的作用

可以看到将 sensor 坐标系映射到屏幕坐标系之后, 还需要顺时针旋转 90° 才能正常显示, 那么其 CameraInfo.orientation 即为 90

好的, 理解了 CameraInfo.orientation 这个参数我们再回过头来看看前后摄像头的旋转算法

2. 后置镜头
public static void setCameraDisplayOrientation(Activity activity,
        int cameraId, android.hardware.Camera camera) {
    ......
    int degrees = 0;
    ......
    int result;
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        ......
    } 
    // back-facing
    else {
        result = (info.orientation - degrees + 360) % 360;
    }
    camera.setDisplayOrientation(result);
}

可以看到后置镜头的计算过程是非常简单的, 摄像头图像方向要恢复到自然方向需要顺时针旋转, 而屏幕逆时针旋转正好抵掉了摄像头的旋转, 所以两者相减, 然后再加上 360 取模运算, 便可以获得最终结果

3. 前置镜头
public static void setCameraDisplayOrientation(Activity activity,
        int cameraId, android.hardware.Camera camera) {
    ......
    int degrees = 0;
    ......
    int result;
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        result = (info.orientation + degrees) % 360;
        result = (360 - result) % 360;  // 抵消镜像
    } 
    // back-facing
    else {
        ......
    }
    camera.setDisplayOrientation(result);
}

前置镜头的代码比后置镜头复杂一些, 我们图解分析一下

前置镜头旋转流程

前置的采集流程如上图所示(前置镜头的 Sensor 坐标系与后置方向不同, 与安装方式有关)

可以看到若是直接按照 CameraInfo.orientation 进行旋转 270°, 那么得到的图像是非镜像的, 显然这是不符合自拍预览效果的

因此系统对其进行了镜像操作(图中的文字没有完全镜像, 请见谅), 那么只需要旋转 90°, 便可以得到想要的预览效果了, 现在再回看上面的代码, 就比较清晰了

三. CameraX 关于尺寸和旋转的处理

CameraX 是 Jetpack 基于 Camera2.0 封装的相机组件, 向 5.0 以上的版本提供服务

关于预览相机相关的配置如下

PreviewConfig config = new PreviewConfig.Builder()
         // CameraX 的宽高比和 Camera1 相反, 为 3:4 9:16......
         .setTargetAspectRatio(new Rational(aspectRatio.getY(), aspectRatio.getX()))
         // 分辨率
         .setTargetResolution(new android.util.Size(previewWidth, previewHeight))
         // 前置与否
         .setLensFacing(facing == Constants.FACING_FRONT ? CameraX.LensFacing.FRONT
                 : CameraX.LensFacing.BACK)
         .build();
Preview preview = new Preview(config);
CameraX.bindToLifecycle(mLifecycleOwner, preview);

可以看到短短几行代码便可以实现最佳预览尺寸的选择, 其旋转的角度在图像通过 SurfaceTexture 输出时, 记录在其 matrix 中, 我们可以很方便进行旋转操作(没有考虑屏幕的旋转, 需要自己矫正)

关于 CameraX 的用法, 想了解更多用法请查看官方文档, 这里就不进行赘述了

总结

关于相机预览尺寸的选取和旋转角度的设定到这里便分析结束了, 这里再简单的回顾一下

  • 相机预览尺寸的选取
    • 统计相机支持的预览尺寸, 按照比例归类
    • 从用户传入的比例中找寻与 View 尺寸相当的作为预览尺寸
  • 相机旋转角度的设定
    • sensor 坐标系
    • sensor 坐标系图像映射到屏幕坐标系
    • 对图像进行校正以确认最终的旋转角度

其中 旋转角度的设定 较之 尺寸的选取 要更为困难, 其中牵扯到坐标系的映射和映射后的旋转, 前置相机还需要考虑镜像的抵消, 不过所幸这里将其梳理清楚了

虽然有 CameraX 这种简单的 API 供我们使用, 但对于 5.0 以下的机型, 仍然需要我们自定义, 因此我们了解其原理是非常重要的, 而且 探索思考的过程比结果更重要 不是?

文中所有的代码地CameraSample中,如有不解,请自行查看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/224085.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 将word转为PDF的三种方式和处理在服务器上下载后乱码的格式

我这边是因为业务需要将之前导出的word文档转换为PDF文件&#xff0c;然后页面预览下载这样的情况。之前导出word文档又不是我做的&#xff0c;所以为了不影响业务&#xff0c;只是将最后在输出流时转换成了PDF&#xff0c;当时本地调用没什么问题&#xff0c;一切正常&#xf…

「X」Embedding in NLP|Token 和 N-Gram、Bag-of-Words 模型释义

ChatGPT&#xff08;GPT-3.5&#xff09;和其他大型语言模型&#xff08;Pi、Claude、Bard 等&#xff09;凭何火爆全球&#xff1f;这些语言模型的运作原理是什么&#xff1f;为什么它们在所训练的任务上表现如此出色&#xff1f; 虽然没有人可以给出完整的答案&#xff0c;但…

c++新经典模板与泛型编程:const修饰符的移除与增加

const修饰符的移除 让你来写移除const修饰符&#xff0c;你会怎么样来写&#xff1f; &#x1f602;&#x1f602;trait类模板&#xff0c;如下 #include <iostream>// 泛化版本 template<typename T> struct RemoveConst {using type T; };// 特化版本 template…

案例059:基于微信小程序的在线投稿系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

18、XSS——cookie安全

文章目录 1、cookie重要字段2、子域cookie机制3、路径cookie机制4、HttpOnly Cookie机制5、Secure Cookie机制6、本地cookie与内存cookie7、本地存储方式 一般来说&#xff0c;同域内浏览器中发出的任何一个请求都会带上cookie&#xff0c;无论请求什么资源&#xff0c;请求时&…

ubuntu中显卡驱动,cuda,cudnn安装

1. 在ubuntu中安装显卡驱动 参考&#xff1a;https://blog.csdn.net/m0_37605642/article/details/119651996 2.在ubuntu中安装cuda 参考&#xff1a;https://blog.csdn.net/m0_61431544/article/details/127007300 2.1 安装cuda cuda官网&#xff1a; https://developer.n…

配置端口安全示例

组网需求 如图1所示&#xff0c;用户PC1、PC2、PC3通过接入设备连接公司网络。为了提高用户接入的安全性&#xff0c;将接入设备Switch的接口使能端口安全功能&#xff0c;并且设置接口学习MAC地址数的上限为接入用户数&#xff0c;这样其他外来人员使用自己带来的PC无法访问公…

基于单片机出租车计价器控制系统

**单片机设计介绍&#xff0c;基于单片机出租车计价器控制系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的出租车计价器控制系统是一个用于控制和管理出租车费用计算的电子设备。下面是一个简单的系统设计介绍&…

【若依系列】1.项目修改成自己包名并启动服务

项目下载地址&#xff1a; 分离版本 https://gitee.com/y_project/RuoYi-Vue 修改工具下载 https://gitee.com/lpf_project/common-tools 相关截图&#xff1a; 1.项目结构&#xff1a; 2.修改包名工具&#xff1a; 工具截图&#xff0c;根据对应提示自定义修改即可&#x…

HarmonyOS4.0从零开始的开发教程08构建列表页面

HarmonyOS&#xff08;六&#xff09;构建列表页面 List组件和Grid组件的使用 简介 在我们常用的手机应用中&#xff0c;经常会见到一些数据列表&#xff0c;如设置页面、通讯录、商品列表等。下图中两个页面都包含列表&#xff0c;“首页”页面中包含两个网格布局&#xff…

为何开展数据清洗、特征工程和数据可视化、数据挖掘与建模?

1.2为何开展数据清洗、特征工程和数据可视化、数据挖掘与建模 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.2节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。内容涵盖数据科学…

AWS基于x86 vs Graviton(ARM)的RDS MySQL性能对比

概述 这是一个系列。在前面&#xff0c;我们测试了阿里云经济版&#xff08;“ARM”&#xff09;与标准版的性能/价格对比&#xff1b;华为云x86规格与ARM&#xff08;鲲鹏增强&#xff09;版的性能/价格对比。现在&#xff0c;再来看看AWS的ARM版本的RDS情况 在2018年&#…

深度学习猫狗分类 - python opencv cnn 计算机竞赛

文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习猫狗分类 ** 该项目较为新颖&a…

华为配置流量抑制示例

如拓扑图所示&#xff0c;SwitchA作为二层网络到三层路由器的衔接点&#xff0c;需要限制二层网络转发的广播、未知组播和未知单播报文&#xff0c;防止产生广播风暴&#xff0c;同时限制二三层网络转发的已知组播和已知单播报文&#xff0c;防止大流量冲击。 配置思路 用如下…

gpt1与bert区别

区别1&#xff1a;网络结构&#xff08;主要是Masked Multi-Head-Attention和Multi-Head-Attention&#xff09; gpt1使用transformer的decoder&#xff0c;单向编码&#xff0c;是一种基于语言模型的生成式模型&#xff0c;更适合生成下一个单词或句子 bert使用transformer的…

Vue 父传子组件传参 defineProps

defineProps 属性&#xff1a;用于接收父组件传递过来的数据。 注意&#xff1a;如果 defineProps 接收的参数名&#xff0c;和已有变量名相同&#xff0c;就会造成命名冲突。 语法格式&#xff1a; // 无限制 const props defineProps([参数名, 参数名]);// 限制数据类型 …

Spring Boot 项目的创建、配置文件、日志

文章目录 Spring Boot 优点创建 Spring Boot 项目创建项目认识目录网页创建&#xff08;了解&#xff09; 约定大于配置Spring Boot 配置文件配置文件格式读取配置项properties 配置文件yml 配置文件基本语法进阶语法配置对象配置集合yml 设置不同环境的配置文件 Spring Boot 日…

springboot——自动装配

自动装配 Condition: Condition内置方法&#xff1a;boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)&#xff0c;返回值为布尔型 重写matches方法的类&#xff1a;SpringBootCondition等 SpringBootCondition&#xff1a;springboot自带的实现类…

QT5.4.1无法打开文件

问题描述&#xff1a;起初是在QT代码中运行打开文件代码&#xff1a; QString gFilename QFileDialog::getOpenFileName(this,"open File",path,"*", nullptr,QFileDialog::DontUseNativeDialog);时&#xff0c;出现了堵塞情况&#xff0c;经过多次实验一…

Qt进程和线程

一、进程 在设计一个应用程序时,有时不希望将一个不太相关的功能集成到程序中,或者是因为该功能与当前设计的应用程序联系不大,或者是因为该功能已经可以使用现成的程序很好的实现了,这时就可以在当前的应用程序中调用外部的程序来实现该功能,这就会使用到进程。Qt应用程序…