Android 使用CameraX实现预览/拍照/录制视频/图片分析/对焦/缩放/切换摄像头等操作

1. CameraX架构

看官方文档 CameraX架构
有如下这一段话

使用CameraX,借助名为"用例"的抽象概念与设备的相机进行交互。

  • 预览 : 接受用于显示预览的Surface,例如PreviewView
  • 图片分析 : 为分析 (例如机器学习) 提供CPU可访问的缓冲区
  • 图片拍摄 : 拍摄并保存图片
  • 视频拍摄 : 通过VideoCapture拍摄视频和音频

不同用例可以组合使用,也可以同时处于活跃状态。
例如,应用中可以加入预览用例,以便让用户查看进入相机视野的画面
加入图片分析用例,以确定照片里的人物是否在微笑
还可以加入图片拍摄用例,以便在人物微笑时拍摄照片

第一次看的时候,一脸懵逼,“用例”,是个什么鬼玩意。

后来,研究了一下,知道"用例"的英文原文叫做Use Case,CameraX中的每一项操作,对应着一种UseCase

  • 预览 : Preview.java
  • 图片分析 : ImageAnalysis.java
  • 图片拍摄 : ImageCapture.java
  • 视频拍摄 : VideoCapture.java

可以看到,这几个类都是继承自UseCase.java类的

public final class Preview extends UseCase {//...
}
public final class ImageAnalysis extends UseCase {//...
}
public final class ImageCapture extends UseCase {//...
}
public final class VideoCapture extends UseCase {//...
}

接下来让我们来尝试使用一下。

2. 前置操作

首先,我们需要新建一个项目,然后引入依赖

// CameraX core library using the camera2 implementation
def camerax_version = "1.2.0-alpha02" //1.2.0-alpha02
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX VideoCapture library
implementation "androidx.camera:camera-video:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
// If you want to additionally add CameraX ML Kit Vision Integration
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:${camerax_version}"

AndroidManifest.xml里添加权限

<!--摄像头权限-->
<uses-permission android:name="android.permission.CAMERA" />
<!--具备摄像头-->
<uses-feature android:name="android.hardware.camera.any" />
<!--存储图像或者视频权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--录制音频权限-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

别忘了申请权限

ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO), 123)

3. 预览 : Preview.java

首先修改activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/camera_container"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/black"><androidx.camera.view.PreviewViewandroid:id="@+id/previewView"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

修改MainActivity.kt

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var cameraProvider: ProcessCameraProviderprivate var preview: Preview? = nullprivate var camera: Camera? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)//TODO 省略了权限申请,具体看文章中 "前置操作" 部分setUpCamera(binding.previewView)}private fun setUpCamera(previewView: PreviewView) {val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =ProcessCameraProvider.getInstance(this)cameraProviderFuture.addListener({try {cameraProvider = cameraProviderFuture.get()bindPreview(cameraProvider, previewView)} catch (e: Exception) {e.printStackTrace()}}, ContextCompat.getMainExecutor(this))}private fun bindPreview(cameraProvider: ProcessCameraProvider,previewView: PreviewView) {//解除所有绑定,防止CameraProvider重复绑定到Lifecycle发生异常cameraProvider.unbindAll()preview = Preview.Builder().build()camera = cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA, preview)preview?.setSurfaceProvider(previewView.surfaceProvider)}
}

看下效果
在这里插入图片描述

4. 图像分析 : ImageAnalysis.java

图像分析用例ImageAnalysis为应用提供可实时分析的图像数据,我们可以对这些图像执行图像处理、计算机视觉或机器学习推断。

val imageAnalysis = ImageAnalysis.Builder()// enable the following line if RGBA output is needed.// .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888).setTargetResolution(Size(1280, 720)).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->val rotationDegrees = imageProxy.imageInfo.rotationDegrees// insert your code here.// 在这里处理图片的解析,比如解析成二维码之类的...// after done, release the ImageProxy objectimageProxy.close()
})

在调用cameraProvider.bindToLifecycle()时,进行传入

cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis
)

5. 拍照 : ImageCapture.java

5.1 仅拍照

这里,我们需要先创建一个imageCapture

imageCapture = ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)//.setTargetAspectRatio(screenAspectRatio)//.setTargetRotation(binding.previewView.display.rotation).build()

然后,在调用cameraProvider.bindToLifecycle()时,进行传入

camera = cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture
)

增加takePicture()方法进行拍照

//进行拍照
private fun takePicture() {imageCapture?.let { imageCapture ->val mainExecutor = ContextCompat.getMainExecutor(this)imageCapture.takePicture(mainExecutor, object : ImageCapture.OnImageCapturedCallback() {override fun onCaptureSuccess(image: ImageProxy) {super.onCaptureSuccess(image)}override fun onError(exception: ImageCaptureException) {super.onError(exception)}})// 让画面闪一下,营造拍照的感觉// We can only change the foreground Drawable using API level 23+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Display flash animation to indicate that photo was capturedbinding.root.postDelayed({binding.root.foreground = ColorDrawable(Color.WHITE)binding.root.postDelayed({ binding.root.foreground = null }, 50L)}, 100L)}}
}

5.2 拍照并保存到本地存储

我们也可以拍照后,保存到本地存储中

/** Helper function used to create a timestamped file */
private fun createFile(baseFolder: File, format: String, extension: String) =File(baseFolder, SimpleDateFormat(format, Locale.US).format(System.currentTimeMillis()) + extension)/** Use external media if it is available, our app's file directory otherwise */
fun getOutputDirectory(context: Context): File {val appContext = context.applicationContextval mediaDir = context.externalMediaDirs.firstOrNull()?.let {File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() }}return if (mediaDir != null && mediaDir.exists())mediaDir else appContext.filesDir
}companion object {private const val FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"private const val PHOTO_EXTENSION = ".jpg"
}//进行拍照并保存到本地
private fun takePictureSaveToDisk() {imageCapture?.let { imageCapture ->// Create output file to hold the imageval photoFile = createFile(getOutputDirectory(this), FILENAME, PHOTO_EXTENSION)Log.i(TAG, "photoFile:$photoFile")// Setup image capture metadataval metadata = ImageCapture.Metadata().apply {// Mirror image when using the front cameraisReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT}// Create output options object which contains file + metadataval outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()// Setup image capture listener which is triggered after photo has been takenimageCapture.takePicture(outputOptions,ContextCompat.getMainExecutor(this),object : ImageCapture.OnImageSavedCallback {override fun onError(exc: ImageCaptureException) {Log.e(TAG, "Photo capture failed: ${exc.message}", exc)}override fun onImageSaved(output: ImageCapture.OutputFileResults) {val savedUri = output.savedUri ?: Uri.fromFile(photoFile)Log.d(TAG, "Photo capture succeeded: $savedUri")// Implicit broadcasts will be ignored for devices running API level >= 24// so if you only target API level 24+ you can remove this statementif (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {application.sendBroadcast(Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri))}// If the folder selected is an external media directory, this is// unnecessary but otherwise other apps will not be able to access our// images unless we scan them using [MediaScannerConnection]val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(savedUri.toFile().extension)MediaScannerConnection.scanFile(application,arrayOf(savedUri.toFile().absolutePath),arrayOf(mimeType)) { _, uri ->Log.d(TAG, "Image capture scanned into media store: $uri")}}})// 让画面闪一下,营造拍照的感觉// We can only change the foreground Drawable using API level 23+ APIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// Display flash animation to indicate that photo was capturedbinding.root.postDelayed({binding.root.foreground = ColorDrawable(Color.WHITE)binding.root.postDelayed({ binding.root.foreground = null }, 50L)}, 100L)}}
}

然后,我们可以在相册里找到这张图片了,图片的真实位置位于/storage/emulated/0/Android/media/你的包名/项目名/中。

6. 视频录制 : VideoCapture.java

视频录制用的是VideoCapture

videoCapture = VideoCapture.Builder()//.setTargetRotation(previewView.getDisplay().getRotation()).setVideoFrameRate(25).setBitRate(3 * 1024 * 1024).build()

在调用cameraProvider.bindToLifecycle()时,进行传入。

camera = cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture 
)

需要注意的是,videoCapture无法和imageAnalysisimageCapture一起使用。
如果同一个页面中这几个功能融合在一起,则需要通过标志位来进行判断。

if (isVideo) {mCamera = cameraProvider.bindToLifecycle(this, cameraSelector,preview, videoCapture);
} else {mCamera = cameraProvider.bindToLifecycle(this, cameraSelector,preview, imageCapture, imageAnalysis);
}

开始录制

private val RECORDED_FILE_NAME = "recorded_video"
private val RECORDED_FILE_NAME_END = "video/mp4"@SuppressLint("RestrictedApi")
private fun startRecording() {//TODO 这里省略了RECORD_AUDIO、PERMISSION_GRANTED权限的判断val contentValues = ContentValues()contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,RECORDED_FILE_NAME + "_" + System.currentTimeMillis())contentValues.put(MediaStore.MediaColumns.MIME_TYPE, RECORDED_FILE_NAME_END)val outputFileOptions = VideoCapture.OutputFileOptions.Builder(getContentResolver(),MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues).build()videoCapture.startRecording(outputFileOptions,ContextCompat.getMainExecutor(this),object : VideoCapture.OnVideoSavedCallback {override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {Log.i(TAG, "视频保存成功:${outputFileResults.savedUri}")}override fun onError(videoCaptureError: Int,message: String,cause: Throwable?) {Log.i(TAG, "当出现异常 cause:$cause")}})
}

停止视频录制

videoCapture.stopRecording()

当我们执行停止视频录制之后,就可以在相册里看到多了一个录制的视频了。

介绍了CameraX里一些常用的UseCase,我们接下来来看下CameraX中的其他一些功能。

7. 切换前后摄像头

我们之前使用cameraProvider.bindToLifecycle()的时候,有一个参数是CameraSelector
CameraX默认给我们提供了前置摄像头和后置摄像头的CameraSelector

public final class CameraSelector {@NonNullpublic static final CameraSelector DEFAULT_FRONT_CAMERA =new CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build();@NonNullpublic static final CameraSelector DEFAULT_BACK_CAMERA =new CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build();//...
}

我们去切换摄像头的时候,就是重新调用一下bindPreview方法,传入新的cameraSelector值就好了

private fun bindPreview(cameraProvider: ProcessCameraProvider,previewView: PreviewView,cameraSelector : CameraSelector) {// 解除所有绑定,防止CameraProvider重复绑定到Lifecycle发生异常cameraProvider.unbindAll()preview = Preview.Builder().build()camera = cameraProvider.bindToLifecycle(this,cameraSelector, preview)preview?.setSurfaceProvider(previewView.surfaceProvider)}

CameraX还为我们提供了判断前置/后置摄像头是否存在的方法

/** Returns true if the device has an available back camera. False otherwise */
private fun hasBackCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
}/** Returns true if the device has an available front camera. False otherwise */
private fun hasFrontCamera(): Boolean {return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
}

看下效果
在这里插入图片描述

8. 对焦

当点击androidx.camera.view.PreviewView的时候,去调用CameraX的对焦方法startFocusAndMetering()就好了。

onCreate()中添加如下代码

binding.previewView.setOnTouchListener { view, event ->val action = FocusMeteringAction.Builder(binding.previewView.getMeteringPointFactory().createPoint(event.getX(), event.getY())).build();showTapView(event.x.toInt(), event.y.toInt())camera?.getCameraControl()?.startFocusAndMetering(action)true
}

增加showTapView()

private fun showTapView(x: Int, y: Int) {val popupWindow = PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT)val imageView = ImageView(this)imageView.setImageResource(R.drawable.ic_focus_view)popupWindow.contentView = imageViewpopupWindow.showAsDropDown(binding.previewView, x, y)binding.previewView.postDelayed({ popupWindow.dismiss() }, 600)binding.previewView.playSoundEffect(SoundEffectConstants.CLICK)
}

看下效果
在这里插入图片描述

9. 缩放

通过GestureDetector监听缩放事件,然后在回调的时候进行执行如下代码,就可以使用双指放大缩小图像

override fun zoom(delta: Float) {val zoomState = camera?.cameraInfo?.zoomStatezoomState?.value?.let {val currentZoomRatio = it.zoomRatiocamera?.cameraControl?.setZoomRatio(currentZoomRatio * delta)}
}

缩放操作具体详见这篇文章 Android使用CameraX实现相机快速实现放大缩小

10. 本文Demo下载

本文的源码Demo详见 : Android CameraX Demo : 实现预览/拍照/录制视频/图片分析/对焦/切换摄像头等操作

推荐阅读 我的另一篇关于Camera的文章 : Android 从零开发一个简易的相机App

参考
Android Developer | CameraX
新技术介绍来了,CameraX 一统江湖?

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 使用Camera2 实现拍照录像的功能

职场小白迷上优美句子: 还是电影 《无问西东》中的台词&#xff0c;这句有点感人&#xff1a; 沈光耀的妈妈对沈光耀说&#xff1a;"当初你离家千里&#xff0c;来到这个地方读书&#xff0c;你父亲和我都没有反对过&#xff0c;因为&#xff0c;是我们想你&#xff0c;…

天猫精灵Aligenie对接记录(三)

原文 https://www.jksxit.com/essay/42 服务端需要开发API接口处理阿里精灵发送的智能设备控制指令&#xff0c;并遵循AliGenie智能家居接入协议。 &#xff08;1&#xff09;同步模式

天猫精灵智能设备对接(4)

apache2php7.0 web服务器的构建 我个人使用的服务器是腾讯云&#xff0c;系统是ubuntu16.04&#xff0c;如果大家要是没有云服务器可以采用花生壳内网穿透的方法&#xff0c;还能得到一个只能在花生壳免费部署的域名&#xff0c;但是也要花6块钱&#xff0c;看需求了&#xff0…

天猫精灵智能家居对接,及天猫iot官网配置图文讲解(二)

天猫精灵智能家居对接,及天猫iot官网配置图文讲解&#xff08;二&#xff09; 2、天猫精灵设备对接 2-1、介绍 ​ 上一章里&#xff0c;我已经讲了天猫精灵的技能配置&#xff0c;设备创建&#xff0c;登录验证这三个部分做了&#xff0c;此次篇文章就讲之后的设备查询&…

天猫精灵智能设备对接(5) SSL https设置

至于什么是SSL证书具体有什么用&#xff0c;想了解的自行百度吧&#xff0c;再不行谷歌&#xff0c;百度出来的东西比我这个外行巴拉巴拉说半天来的痛快&#xff0c;本来腾讯云是送免费一年的SSL证书的&#xff0c;但是我赶得非常不巧&#xff0c;在我做测试那几天腾讯的ssl服务…

天猫精灵智能设备对接(8) 开发者网关地址

洋洋洒洒六七千字已经搭进去了&#xff0c;终于把服务器篇写的差不多了&#xff0c;当然小狂不是专业的写手&#xff0c;有些东西写的凑合看吧&#xff0c;只是说明过程&#xff0c;并不修饰言辞&#xff0c;看的舒服就点个赞&#xff0c;不舒服就当学东西了吧。这篇文章我们将…

springboot项目接入天猫精灵

springboot项目接入天猫精灵 最近工作需要使用到天猫精灵的语音功能&#xff0c;大体是通过呼叫对应的“调用词”实现携带参数&#xff0c;然后调用我项目中的接口&#xff0c;以实现对应的业务。所以在此简单的记录下使用过程 实际上&#xff1a;天猫精灵的官方文档记录的也很…

天猫精灵智能家居对接,及天猫iot官网配置图文讲解(一)

天猫智能家居对接 1-1、介绍 这篇文章主要是介绍&#xff0c;如何使用java对接天猫精灵智能家居提供的api。这么做的好处就是能让用户通过天猫精灵发送命令到我们的服务器&#xff0c;然后操控设备执行一系列的命令&#xff0c;当然这些功能呢都是天猫精灵官方制定的协议&…

java对接天猫精灵语音助手实现对公司其下的智能设备进行控制(附上源码)

java对接天猫精灵语音助手实现对公司其下的智能设备进行控制 前言当初刚来广州 公司上一任java已经离职半年 &#xff0c;项目已经跑不动了&#xff0c;才招人的&#xff0c;所以我获得的是一个连跑都跑不起来的项目源码并且对项目一无所知&#xff0c;一年前网上并没有对接天…

天猫精灵智能设备对接(7) OAuth2.0

在开始之前先放两篇参考&#xff0c;一篇英文http://bshaffer.github.io/oauth2-server-php-docs/cookbook/&#xff0c;一篇中文https://www.cnblogs.com/endv/p/7868549.html&#xff0c;中文博客里的内容基本上是把英文官方文档翻译一遍。到这里如果你不知道OAuth2.0是啥可以…

天猫精灵对接2(OAuth 搭建)

根据 接入方式及流程 中的说明&#xff0c;可知&#xff0c;搭建过程中&#xff0c;我们需要自己整一个 OAuth 的授权平台&#xff0c;具体说明可以参考蟋蟀大哥的文章 ASP.NET WebApi OWIN 实现 OAuth 2.0 &#xff0c;我的实际代码也是基于文章给出的源码修改的。 第一步 认…

天猫精灵对接智能设备

why to do&#xff1a;   我之前一直很喜欢智能家居&#xff0c;可惜的是现在市场上成品的智能家居实在是太贵了&#xff0c;屌丝的码农是在背不起每月高额的房贷和装修费用的基础上&#xff0c;再买成品的智能设备&#xff08;像某米那样一个智能开关&#xff0c;竟然卖那么…

从零玩转系列之SpringBoot3-核心原理

一、简介 1.前置知识 ● Java17 ● Spring、SpringMVC、MyBatis ● Maven、IDEA 2.环境要求 环境&工具版本(or later)SpringBoot3.1.xIDEA2023.xJava17Maven3.5Tomcat10.0Servlet5.0GraalVM Community22.3Native Build Tools0.9.19 二、SpringBoot3-核心原理 1.事件和监听器…

SpringBoot3【⑤ 核心原理】

1. 事件和监听器 1. 生命周期监听 场景&#xff1a;监听应用的生命周期 1. 监听器-SpringApplicationRunListener 自定义SpringApplicationRunListener来监听事件&#xff1b; 1.1. 编写SpringApplicationRunListener 这个接口的实现类 1.2. 在 META-INF/spring.factories …

开发必备,开源 or 免费的 AI 编程助手

AI 大模型的火热&#xff0c;让开发圈近来如虎添翼&#xff0c;各种各样基于 AI 技术的开发者工具和新范式不断涌现&#xff0c;尤其是 Github 和 OpenAI 共同推出的 Copilot X &#xff0c;更是一骑绝尘。本文推荐一些开源 or 免费的 AI 编程工具&#xff0c;不妨试着用起来。…

超过5000人的2年研究表明,这一活动破坏你的身心健康

Tips 原文作者&#xff1a;Minda Zetlin 原文出处&#xff1a;A 2-Year Study of More Than 5,000 People Shows This 1 Activity Destroys Your Emotional and Physical Health 阅读时&#xff0c;把文中的 Fackbook 换成微信。 国外主要用 Facebook&#xff1b; 国内主要是微…

申请阿里云服务器并搭建公网可支持数据上传下载的HTTP服务器

1. 前言 拥有一台自己的云服务器可以做很多事情。阿里云服务器毫无疑问是国内最好的。 阿里云服务器可以用于各种互联网应用的搭建和运行&#xff0c;提供稳定、高性能的服务。 阿里云服务器的用途&#xff0c;包括但不限于以下几个方面&#xff1a; 网站托管&#xff1a;可以将…

谷歌眼镜秀出时尚风采:对面的女孩看过来

摘要&#xff1a;在近日举办的纽约时尚周上&#xff0c;让身材火辣的模特带上谷歌的眼镜&#xff0c;行走在T台之上。主打时尚牌&#xff0c;进一步加固谷歌眼镜在大众消费阶层的印象&#xff0c;尤其是女性消费者。谷歌眼镜创始人Sebastian Thrun指出&#xff1a;谷歌眼镜特别…

学生台灯什么牌子好对眼睛好?专业护眼灯的学生台灯分享

据报告统计&#xff0c;2022年我国儿童青少年总体近视率为52.7%&#xff0c;其中6岁儿童为14.3%&#xff0c;小学生为35.6%&#xff0c;初中生为71.1%&#xff0c;高中生为80.5%&#xff0c;这些数据让人不寒而栗&#xff01; 专家表示&#xff0c;导致儿童青少年近视的因素&am…

【UGP VR眼镜排行榜】2018VR眼镜眼镜哪个好?什么VR眼镜值得买?综合推荐十大热品

科技的发展&#xff0c;高科技产品层出不穷&#xff0c;VR眼镜的出现使人们足不出户也能享受到高品质的观影感受。VR(Virtual Reality&#xff09;即虚拟现实&#xff0c;简称VR.虚拟现实头戴显示器设备&#xff0c;简称VR头显VR眼镜.现在&#xff0c;VR眼镜已不是什么稀奇的东…