Skip to main content

相机功能实现教程

1. 概述

使用Mobile SDK 拍摄照片和视频,可以选择很多不同的参数设置,分辨率,帧率,曝光设置,图片设置和文件类型,本教程旨在帮助您基本了解MobileSDK 相机相关接口的使用。 我们将实现三个基本相机功能:拍照视频录制 以及相机参数设置

注: 拍照和录像展示功能依赖于视频流的解码和渲染,请参考: 视频解码功能实现文档,将为您介绍如何快速的接入相机的视频流,以及如何渲染到手机屏幕上。当然您也可以直接发送参数命令。

下面以Autel_UX_SDK demo为例讲解如何进行拍照、录像、参数设置功能开发

2. 导入SDK

  • Mobile SDK 提供访问无人机各个功能模块接口API。通过Mobile SDK,开发者可以实现自主飞行,控制相机和云台,接收实时视频图传和传感器数据,下载保存好的媒体文件,以及监听其他组件的状态等等。
  • AutelUXFramework 是Autel封装的基础功能库,提供一些工具类,基础控件,基础组件如地图组件等

2.1 导入Mobile SDK

导入AUTELSDK,包括三个 Frameworks:AUTELSDK.xcframework、AUTELWidget.xcframework、DDSimpleNetLogger.xcframework。

参考 Autel_UX_SDK: Target -> Build Phases -> 左上⻆+号 -> New Copy Files Phase 然后在Embed Frameworks 下 Destination选择Frameworks

AutelSDKFrameworks

2.2 导入AutelFramework

参考Autel_UX_SDK: TARGET > General > Frameworks, Libraries, and Embedded content 中导入 AutelUXFramework.framework

AutelSDKFrameworks

3. 相机接口类介绍

在本例中,为方便相机功能开发,我们对底层Mobile SDK相机接口做了第二次封装,主要涉及的类有ATCameraCommandDeliverCameraParametersGetterCameraParametersSetter

  • ATCameraCommandDeliver: Mobile SDK相机接口再次封装类
  • CameraParametersGetter:相机获取接口集合类,将ATCameraCommandDeliver类中所有get接口集中放在这里
  • CameraParametersSetter:相机设置接口集合类,将ATCameraCommandDeliver类中所有set接口集中放在这里

它们之间的调用关系如下:

接口关系图

4. 接口功能介绍

相机接口较多,这里不做一一介绍。我们已经将所有相机接口封装到了ATCameraCommandDeliver类中,参考此类,您可以知道如何调用Mobile SDK相机接口。这里介绍下相机接口的主要功能。

  • 相机模式: 视频录制模式和静态图像拍摄模式
  • 曝光: 快门,ISO,光圈和曝光补偿
  • 图像参数: 屏幕长宽比,对比度,色相,清晰度,饱和度、滤镜、拍照格式
  • 视频参数: 分辨率、帧频、编码格式
  • 相机朝向: 相机安装在云台上时,相机的朝向可以通过接口控制

5. 拍照/录像功能开发

5.1 创建图传界面

本例为方便拍照、录像切换,拍照ViewController和录像ViewController都作为图传界面的子ViewController,图传界面是整个视图的载体。所以首先创建图传界面ATConsoleViewController 图传ViewModel ATConsoleViewModel

ATConsoleViewController 或者ATConsoleViewModel必须遵循CameraProtocolDroneProtocol协议才能实时获取相机或者飞机的状态或者参数上报。

注意:若不是继承于BaseViewController的类但继承于NSObject 必须调用以下代码才能注册Protocol,否则遵循的Protocol将不能生效。BaseViewController已经自动注册Protocol,不必再次调用。

  func registerProtocol() {
registerAutelProtocolsIfCan()
}

当类销毁时,需要调用以下以进行注销:

  func  removeProtocol() {
removeAutelProtocolsIfCan()
}

CameraProtocol

public protocol CameraProtocol : AutelUXFramework.CheckProtocol {

func camera(_ camera: AUTELBaseCamera!, didUpdateConnectState connectState: AUTELCameraConnectState)

func camera(_ camera: AUTELBaseCamera!, didUpdateRecordRecoverState recoverState: AUTELCameraRecordRecoverState)

func camera(_ camera: AUTELBaseCamera!, didGenerateNewMediaFile newMedia: AUTELMedia!)

func camera(_ camera: AUTELBaseCamera!, didUpdateSDCardState sdCardState: AUTELCameraSDCardState!)

func camera(_ camera: AUTELBaseCamera!, didUpdateMMCState mmcState: AUTELCameraSDCardState!)

func camera(_ camera: AUTELBaseCamera!, didUpdateSystemState systemState: AUTELCameraSystemBaseState!)

func camera(_ camera: AUTELBaseCamera!, didUpdateParameters parameters: AUTELCameraParameters!, change: [AnyHashable : [Any]]!)

func camera(_ camera: AUTELBaseCamera!, didUpdateCurrentExposureValues exposureParameters: AUTELCameraExposureParameters!)

func camera(_ camera: AUTELBaseCamera!, didUpdateHistogramTotalPixels totalPixels: Int, andPixelsPerLevel pixelsArray: [Any]!)

func camera(_ camera: AUTELBaseCamera!, didUpdateTimeLapseCaptured captureCount: Int, andCountDown timeInterval: TimeInterval)

func camera(_ camera: AUTELBaseCamera!, didUpdateAutoFocusState focusState: AUTELCameraAFLensFocusState, andLensFocusAreaRowIndex rowIndex: Int, andColIndex colIndex: Int)

func camera(_ camera: AUTELBaseCamera!, didUpdateAeAfState state: AUTELCameraAFWorkStatus)
}

这里会主要用到到以下CameraProtocol,以获取相机的参数的实时状态,其他的Protocol会在相册或者或者直方图的模块使用到,将在具体模块中讨论


extension ATConsoleViewModel: CameraProtocol{
func camera(_ camera: AUTELBaseCamera!, didUpdateConnectState connectState: AUTELCameraConnectState) {
guard connectState == .connected else {
printLog("Camera Unconnect")
return
}
updateCameraWorkMode()
}

func camera(_ camera: AUTELBaseCamera!, didUpdateSystemState systemState: AUTELCameraSystemBaseState!) {
DispatchQueue.main.async {
self.mediaViewModel?.loadSystemState(systemState)
self.setupThumbnail()
}
}

}

当相机连接上时,可以通过以下代码主动获取一次相机的工作模式:

func updateCameraWorkMode() {
ATCameraCommandDeliver.getWorkMode { (workMode, error) in
if let err = error {
printLog("get camera work mode failure.\(err.localizedDescription)")
return
}
let workMode = workMode.toAT()
self.workModelHandle?(workMode)
}
}

相机支持的工作模式有如下。本文只对拍照(单拍/连拍/AEB/定时拍)和录像进行讨论。

public enum AUTELCameraWorkMode : UInt8, @unchecked Sendable {

/**
* @brief Single shot
*
* 单拍
*/
case captureSingle = 0

/**
* @brief Video mode
*
* 录像
*/
case recordVideo = 1

/**
* @brief Burst
*
* 连拍
*/
case captureBurst = 2

/**
* @brief Timelaspe
*
* 定时拍
*/
case captureInterval = 3

/**
* @brief AEB
*
* AEB
*/
case captureAEB = 4

/**
* @brief Panorama
*
* 全景拍摄
*/
case capturePanorama = 5


/**
* @brief Slow Motion
*
* 慢动作录影
*/
case recordVideoSlowMotion = 6

/**
* @brief Looping
*
* 循环录影
*/
case recordVideoLooping = 7


/**
* @brief Moving TimeLapse
*
* 移动延时摄影
*/
case captureMovingTimeLapse = 8

/**
* @brief HDR
*
* HDR
*/
case captureHDR = 9

/**
* @brief MFNR
*
* 纯净夜拍
*/
case captureMFNR = 10

/**
* @brief Camera work mode unknown
*
* 未知工作模式
*/
case unknown = 255
}

获取相机工作模式后即可根据相机工作模式,更新右侧面板UI,决定是应该展示拍照还是录像ViewController

extension ATCameraWorkMode{
func createCameraVC() -> ATCameraViewController {
let vc: ATCameraViewController
switch self {
case .video:
vc = ATVideoCameraViewController()
case .photo:
vc = ATPhotoCameraViewController()
}
return vc
}
}


func updateCameraVC(workMode: ATCameraWorkMode) {
let vc = workMode.createCameraVC()
addChild(vc)
view.insertSubview(vc.view, belowSubview: contentView.rightView)
cameraVC?.view.removeFromSuperview()
cameraVC?.removeFromParent()
cameraVC = vc
isLanscape ? showLanscape() : showPortrait()
}

5.2 创建右侧操作面板

这里为了方便切换拍照/录像 创建相机拍照录像切换和拍照录像按钮UI ATConsoleRightControlView, 视图如下:

image/image-20220802095621882.png

相机拍照/录像工作模式切换代码如下:

  private func setCameraWorkMode(_ workMode: ATCameraWorkMode){
cameraWorkMode = workMode
CameraParametersSetter.setParameterWithBranch(.workMode, andValue: workMode.toAutel().rawValue)
}

5.3 创建拍照视图

创建拍照视图ATPhotoCameraViewController ,拍照视图只承载一些与拍照相关的特定功能和手势功能,真正相机的视频流的渲染还是位于图传视图ATConsoleViewController 中。

拍照命令和停止拍照发送如下。处于单拍/AEB/连拍模式下不需要发送停止命令,处于定时拍时,需要发送停止命令,相机才会停止拍照。

 func takePhoto() {
ATCameraCommandDeliver.takePhoto { (error) in
if let error = error {
printLog("take photo error: \(error.localizedDescription)")
}
}
}

func stopPhoto() {
ATCameraCommandDeliver.stopPhoto { (error) in
if let error = error {
printLog("stop photo error: \(error.localizedDescription)")
}
}
}

相机拍照时可以通过相机的Protocol监听相机拍照状态的改变,以改变右侧操作面板的视图变化:

override func loadSystemState(_ systemState: AUTELCameraSystemBaseState!) -> Void {
super.loadSystemState(systemState)
isShooting.accept(systemState.isShooting)
updatePhotoMode()
if systemState.isRunning {
state.accept(.running)
return
}
if state.value == .running{
state.accept(.finish)
return
}
state.accept(systemState.mode.toAT() == .photo ? .ready : .disable)
}

5.4 创建录像视图

创建拍照视图ATVideoCameraViewController ,录像视图只承载一些与录像相关的特定功能和手势功能,真正相机的视频流的渲染还是位于图传视图ATConsoleViewController 中。ATVideoCameraViewControllerATPhotoCameraViewController 均继承于ATCameraViewController

开始录像和停止录像命令发送如下:

func startRecord() {
ATCameraCommandDeliver.startRecording { (error) in
if let error = error {
printLog("start recording error: \(error.localizedDescription)")
}
}
}
func finishRecord() {
ATCameraCommandDeliver.stopRecording { (error) in
if let error = error {
printLog("stop recording error: \(error.localizedDescription)")
return
}
self.state.accept(.finish) //正在结束
}
}

相机拍照时可以通过相机的Protocol监听相机录像状态的改变,以改变右侧操作面板的视图变化:

override func loadSystemState(_ systemState: AUTELCameraSystemBaseState!) -> Void {
super.loadSystemState(systemState)
isShooting.accept(systemState.currentPivStatus == .photoTaking)
currentRecordSecondsText.onNext(currentRecordSecondsText(Int(systemState.currentVideoRecordingTimeInSeconds)))
if !CameraSDCardState.shared.isVerified {
currentRemainSecondsText.onNext(currentRecordSecondsText(CameraSDCardState.shared.availableRecordingTime()))
}
if systemState.isRecording && state.value != .finish {
state.accept(.running)
return
}
if systemState.isRecording {
return
}
state.accept(systemState.mode == .recordVideo ? .ready : .disable)
}


func currentRecordSecondsText(_ seconds: Int) -> String {
return "\((seconds / 3600).format(format: "02")):\((seconds / 60).format(format: "02")):\((seconds % 60).format(format: "02"))"
}

6. 参数设置

相机参数设置有较多的设置项,为方便查看,我们设计了一个枚举以记录。

相机参数设置项:

/// 相机可以 使用 ParametersAction 设置、获取 的参数
enum CameraMenuBranch: String {
case photoFormat = "PhotoFormat"
case videoFormat = "VideoFormat"
/// 拍照分辨率
case photoSize = "size"
/// 拍照模式
case workMode = "mode"
/// 曝光模式
case exposureMode = "exposure"
/// 感光度
case iso = "iso"
/// iso模式
case isoMode = "iso mode"
/// 曝光补偿
case exposureCompensation = "ev"
case digitalFilter = "color"
case videoStandard = "standard"
/// 视频分辨率
case videoResolution = "Resolution"
/// 视频帧率
case frameRate = "FrameRate"
/// 数码变焦 af & ae
case digitalZoom = "digital zoom"
case piv = "piv"
/// 快门速度
case shutterSpeed = "shutter"

case hdr = "hdr"
case mfnr = "mfnr"
case irColor = "ir color" // 对应 "menu": "ir color",
case overlapOffset
/// 双光 热成像显示模式 AUTELCameraThermalDisplayMode
case display
/// 拍照模式- 连拍
case photoBurst = "burst"
/// 拍照模式- AEB(自动包围曝光)
case photoAeb = "aeb"
/// 拍照间隔
case photoTiming = "timelapse"

//------ 白平衡
/// 白平衡
case whiteBalance = "wb"
/// 白平衡-自定义
case wbCustom = "wb custom"
//------
//视频压缩标准
case encodingFormat = "VideoEncodingStandard"

/// 透雾 isOn
case dehaze
/// 透雾值
case dehazeValue
/// 增强对焦 isOn
case roi
/// 增强对焦值
case roiValue
/// 热成像温度告警属性
case irTempAlarm
/// 热成像辐射率
case irTempEmit
/// 热成像图片模式
case irImageMode
/// 热成像图片模式-对比度和亮度
case irImageModeParm
/// 热成像图像增强
case irEnhance
/// 热成像图像去噪开关
case irNr
/// 热成像增益
case irGain
/// 热成像等温线模式
case irIsoThermMode
/// 热成像等温线阈值
case irIsoThermModeParm
/// 对焦模式
case af
/// aeaf 锁
case aeLock
/// 点测光行数[1...15]
case spotRow
/// 点测光列数[1...23] xt701 [1...17]
case spotCol
/// 点测光
case spotCoordinate
/// 光圈
case aperture

//----- 拍照风格
/// 拍照风格
case style
/// 拍照风格-自定义
case styleCustom = "style custom"
/// 拍照风格-自定义-锐度
case sharpness
/// 拍照风格-自定义-对比度
case contrast
/// 拍照风格-自定义-饱和度
case saturation
/// 拍照风格-自定义-色度
case hue
//----- 拍照风格

//相机设置
case grid
case centerPoint
case antiFlicker
case captionEnable
case histogram
case afFocusAssist
case mfFocusAssist
case store
case gimbalLockState//云台锁定状态

// 云台角度
case gimbalAngle = "gimbal"

/// 延时摄影拍照格式
case photoFormatDelayShot = "phofor delayshot"

case unknown
}

在相机连接上后可以统一拿一次相机参数的所有值:

 CameraParametersGetter.loadAllParameters()

当然您也可以单独获取需要某一个相机参数项,后面我们将具体讲到。

我们设计了CameraSelection这样一个类去存储相机参数,当获取相机参数后,我们在其他地方有使用时可以直接通过此类进行获取。

比如获取相机工作模式:

let workMode =  CameraSelection.shared.valueWith(.workMode).realData

对于某个具体的相机参数设置项,可能存在子的参数设置项,为了方便管理,我们定义了一个CameraMenuItem去管理相机参数。

下面我们将将展示如何使用这个类获取相机视频分辨率:

  let menu = CameraMenuItem(style: CameraMenuStyle.branch(.videoResolution)) //生成对象
let menuSub = menu.getNewChildren() ?? [] //菜单的子菜单
let select = CameraMenuItem.findSelectedIndexPathJustInParent(menuSub) ?? 0 // 当前选中的菜单
let arry = CameraModelMenuItemHandler.parseSubMenuItems(item: menu) ?? [] // 子菜单展示名【3840x2160......】

单独获取某个相机参数(以获取相机拍照分辨率为例):

 CameraParametersGetter.getPhotoSize(with: { [weak self] (error) in
guard let self = self else { return }
if error == nil {
let value = CameraSelection.shared.valueWith(.photoSize).realData.digitalValue() ?? 0
prin("size \(value)")
}
})

设置相机参数(以拍照图片格式为例):

let value = AUTELCameraPhotoFileFormat.DNG.rawValue
CameraParametersSetter.setParameterWithBranch(CameraMenuBranch.photoFormat,
andValue: value,
with: { (error) in
guard let self = self else { return }
if error == nil {
let value = CameraSelection.shared.valueWith(.photoFormat).realData.digitalValue() ?? 0
prin("photoFormat \(value)")
}
})

根据以上内容,您可以通过同样的方式展示或者修改相机参数项。

7. 总结

在本教程中,您已经学习了如何使用MobileSDK 如何进行拍照/录像以及相机参数设置开发。希望您喜欢本教程,并继续关注我们的下一个教程!