Unity控制佳能单反拍照及数据获取

  • Post author:
  • Post category:其他


市面比较常用佳能单反,其他单反是否也提供SDK没有搜过。

佳能单反一般选用EOS500D,550D,600D,650D,750D这些都是被EDSDK所支持的。


截止2019年年初,佳能官方EDSDK需要在官网申请,不对中国提供。

我是使用的EDSDK的版本是3.5/3.6.1


单反侧面翻盖打开后,一般会看到两个接口,一个是micro usb,一个是mini hdmi。前者是本文章主要介绍的方式所使用的,即通过micro usb 转 usb线连接单反与主机

单反开机,把拨盘拨到M挡

如果觉得原配的usb线太短,可以加一根usb延长线,根据距离考虑是否要自带放大器的


EDSDK的dll和c#脚本导入之后如下

我们主要需要关注的是SDKHander.cs。它定义了变量、事件、连接设备方法和摄像机命令。

STAThread是一个线程管理脚本。


1 初始化

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EDSDKLib;
using UnityEngine.UI;
using EDSDK.NET;

public class Main : MonoBehaviour {

    SDKHandler CameraHandler;
    EDSDK.NET.Camera myCamera;

    #region UI变量
    public RawImage moviePlane;
    private Texture2D tt;
    #endregion

    #region 设备可用变量
    private string avValues = "";
    private string tvValues = "";
    private string isoValues = "";
    #endregion

    // Use this for initialization
    void Start () {
        //实例化单反操控类
        CameraHandler = new SDKHandler();
        print("EDSDK 初始化成功");
        
        //找到设备
    }
}

2 找到设备

        //找到设备
        List<EDSDK.NET.Camera> ll = CameraHandler.GetCameraList();
        foreach (EDSDK.NET.Camera cc in ll)
            print(cc.Info.szDeviceDescription);
        if (ll.Count == 0)
        {
            print("没有发现佳能单反设备");
            return;
        }
        myCamera = ll[0];

        //设置设备

3 设置设备

        //了解设备参数

        //拍摄模式,就是主拨盘档位,没有意义,自己在相机上拨就好了
        //我试的时候,需要拨到M档,如果需要获取视频流,必须是录像档位
        //foreach (int ii in CameraHandler.GetSettingsList(EDSDK.PropID_AEModeSelect))
        //    print("AEMode = " + ii);

        //光圈
        foreach (int ii in CameraHandler.GetSettingsList(EDSDKLib.EDSDK.PropID_Av))
            avValues += ii + "|";
        print("Av = " + avValues);

        //快门
        foreach (int ii in CameraHandler.GetSettingsList(EDSDKLib.EDSDK.PropID_Tv))
            tvValues += ii + "|";
        print("Tv = " + tvValues);

        //感光度
        foreach (int ii in CameraHandler.GetSettingsList(EDSDKLib.EDSDK.PropID_ISOSpeed))
            isoValues += ii + "|";
        print("ISO = " + isoValues);

        //以下不重要
        //foreach (int ii in CameraHandler.GetSettingsList(EDSDK.PropID_MeteringMode))
        //    print("Metering = " + ii);
        //foreach (int ii in CameraHandler.GetSettingsList(EDSDK.PropID_ExposureCompensation))
        //    print("Exposure = " + ii);
        
        //设置设备
        CameraHandler.SetSetting(EDSDKLib.EDSDK.PropID_Av, 80);//光圈
        CameraHandler.SetSetting(EDSDKLib.EDSDK.PropID_Tv, 61);//快门速度
        CameraHandler.SetSetting(EDSDKLib.EDSDK.PropID_ISOSpeed, 104);//ISO
        //Save To有三种,相机存储卡、本地即电脑、两者都保存。这里选保存到主机,单反没配存储卡
        CameraHandler.SetSetting(EDSDKLib.EDSDK.PropID_SaveTo, (uint)EDSDKLib.EDSDK.EdsSaveTo.Host);//图片保存位置
        CameraHandler.SetCapacity();//这句必须加,曾经不明白这句的API解释而含累错过
        CameraHandler.ImageSaveDirectory = Application.streamingAssetsPath;//把拍到的照片保存到streamingAsset下

        //显示输出

数值对应表:

光圈

快门

4 预设实时视频流的显示

这个纯粹就是准备好一个RawImage组件,准备播放。

     tt = new Texture2D(1920, 1080,TextureFormat.RGB24,true);
     moviePlane.texture = tt;

5 控制与输出

    private bool isLiveOn = false;
    private byte[] tempBytes;
    // Update is called once per frame
    void Update () {
        if (Input.GetKeyDown(KeyCode.S))
        {
            //拍照
            CameraHandler.TakePhoto();
        }
        if (Input.GetKeyDown(KeyCode.D))
        {
            //开直播
            CameraHandler.StartLiveView();
            isLiveOn = true;
        }
        if (Input.GetKeyDown(KeyCode.F))
        {
            //退出直播间~~
            CameraHandler.StopLiveView();
            isLiveOn = false;
        }
        if (isLiveOn)
        {
            //如果直播在开着,就一帧帧load显示。这里才是有坑的地方。
            tempBytes = CameraHandler.GetImageByte();
            tt.LoadImage(tempBytes);
        }
    }

6 录像视频流的API改造

佳能的EDSDK提供的cs脚本并没有针对Unity,Unity C#的开发和.net framework的开发最突出的问题就是:C#里面的Bitmap在Unity里面没有,怎么把Bitmap转换成Texture呢?

在SDKHandler.cs里面需要做一些改动

1 新增函数和变量

        public byte[] TextureBytes;//byte数组,外部获取
        public byte[] GetImageByte()
        {
            if (TextureBytes != null && TextureBytes.Length != 0)
                return TextureBytes;
            return null;
        }
        public Bitmap TextureBitmap;//Bitmap中转变量
        private MemoryStream ms;//转换中的关键变量

2 找到DownloadEvf函数,在函数体内做如下修改

    //run live view
	while (IsLiveViewOn)
	{
		//download current live view image
		err = EDSDKLib.EDSDK.EdsCreateEvfImageRef(stream, out EvfImageRef);
		if (err == EDSDKLib.EDSDK.EDS_ERR_OK) err = EDSDKLib.EDSDK.EdsDownloadEvfImage(MainCamera.Ref, EvfImageRef);
		if (err == EDSDKLib.EDSDK.EDS_ERR_OBJECT_NOTREADY) { Thread.Sleep(4); continue; }
		else Error = err;
		/*
		lock (STAThread.ExecLock)
		{
			//download current live view image
			err = EDSDKLib.EDSDK.EdsCreateEvfImageRef(stream, out EvfImageRef);
			if (err == EDSDKLib.EDSDK.EDS_ERR_OK) err = EDSDKLib.EDSDK.EdsDownloadEvfImage(MainCamera.Ref, EvfImageRef);
			if (err == EDSDKLib.EDSDK.EDS_ERR_OBJECT_NOTREADY) { Thread.Sleep(4); continue; }
			else Error = err;
		}
		*/

		//get pointer
		Error = EDSDKLib.EDSDK.EdsGetPointer(stream, out jpgPointer);
		Error = EDSDKLib.EDSDK.EdsGetLength(stream, out length);

		//get some live view image metadata
		if (!IsCoordSystemSet) { Evf_CoordinateSystem = GetEvfCoord(EvfImageRef); IsCoordSystemSet = true; }
		Evf_ZoomRect = GetEvfZoomRect(EvfImageRef);
		Evf_ZoomPosition = GetEvfPoints(EDSDKLib.EDSDK.PropID_Evf_ZoomPosition, EvfImageRef);
		Evf_ImagePosition = GetEvfPoints(EDSDKLib.EDSDK.PropID_Evf_ImagePosition, EvfImageRef);

		//release current evf image
		if (EvfImageRef != IntPtr.Zero) { Error = EDSDKLib.EDSDK.EdsRelease(EvfImageRef); }

		//create stream to image
		unsafe { ums = new UnmanagedMemoryStream((byte*)jpgPointer.ToPointer(), (long)length, (long)length, FileAccess.Read); }
        
        //把unmanageedMemoryStream类型的ums转成Bitmap
		TextureBitmap = new Bitmap(ums, true);
		ms = new MemoryStream();
        //用System.Drawing.Bitmap的方法将Bitmap转成MemoryStream
		TextureBitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        //将MemoryStream转成Bytes
		TextureBytes = ms.GetBuffer();

		//fire the LiveViewUpdated event with the live view image stream
		if (LiveViewUpdated != null) LiveViewUpdated(ums);
		ums.Close();
	}

主要注意这一段:

7 关闭会话

    void OnApplicationQuit()
    {
        //CameraHandler.StopLiveView();
        CameraHandler.Dispose();
    }

在这个基础上可以实现拍照,照片会产生在StreamingAssets文件夹下,可以开启实时录像视频流,但是视频流观察下来有0.5秒-1秒的延迟。对于互动应用,如果需要实时画面让用户看到效果,不会影响需求实现,但是如果对实时性要求比较高,这个延迟是不行的,市面上我见过直接使用micro usb接口实现的单反拍照应用不延迟,我目前想应该不是通过unity实现的,还有一点,由于用到了STAThread脚本去管理一个线程,这个线程会在打开实时录像时使用,一旦使用,在exe状态下,关闭应用程序会导致卡死,后续我也会继续去研究怎么改进。

其他方案

视频采集卡

单反有mini hdmi接口

我们可以用mini hdmi 转hdmi线,连接单反与采集卡

视频采集卡,无理是pcie内置采集卡,还是外置采集卡都可以


如果是PCIE的内置采集卡,Unity可以通过WebCamDevice去获取到这个“摄像”设备

如果是外置采集卡,则用采集卡另一端的USB3.0接口与主机连接,同样,Unity也是通过WebCamDevice去获取。

这样的技术方案,没有延迟。

缺点是连接了mini hdmi 档位需要在摄像档位,此时连接micro usb无法通过程序进行控制。



这个需求从当初研究到现在已经很长时间,如果有些地方写的不对或者不够好,欢迎您的指正。


工程源码参考:

Unity控制佳能单反工程



版权声明:本文为u010941527原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。