C# 中的 Canon EDSDK 教程






4.95/5 (146投票s)
一个关于 Canon SDK 的教程,用于远程控制 DSLR 相机。从拍照到使用 LiveView。
新版本下载
旧版本下载
引言
Canon EOS Digital SDK 是一个功能强大的 SDK,用于远程控制 Canon DSLR。不幸的是,在互联网上很难找到一些好的示例,而且提供的文档也不够完整。由于我已经发现了许多东西,并希望让其他人更容易上手,所以我认为可以整理一些最重要的内容并做一个教程。
本教程和库是用 C# 编写的,但它符合 CLS 标准,因此可以从任何 .NET 语言中使用。
本教程包含
- 初始化和终止 SDK
- 获取已连接的相机
- 打开和关闭与相机的会话
- 获取相机设置
- 设置相机设置
- 获取可用设置列表
- S向相机发送命令
- 拍照(普通模式和 B 门模式)
- 录制视频
- 从相机下载数据(图像、影片)
- 启动和查看实时视图
- 控制对焦
- 锁定和解锁相机 UI
- 相机上的文件夹和文件
- 获取图像缩略图
注意:我与佳能公司没有任何隶属关系或资金支持。
我对本软件不作任何保证。请自行承担使用风险!
背景
您必须拥有 Canon EDSDK 的副本才能使其正常工作。我不允许在项目中包含 DLL 文件,因此您必须在此处申请获取它们
获取 DLL 文件后,将它们放在可执行文件旁边。否则,如果主 DLL 调用子 DLL,将会出现问题。
为了使某些方法正常工作,使用相机的全手动或至少半自动模式也很重要。
Using the Code
该解决方案由四个项目组成
EDSDKLib
:处理所有 SDK 和相机操作的主要项目WinFormsExample
:一个示例项目,它使用EDSDKLib
并在 Windows Forms UI 应用程序中使用它WpfExample
:一个示例项目,它使用EDSDKLib
并在 WPF UI 应用程序中使用它ConsoleExample
:一个示例项目,它使用EDSDKLib
并在控制台应用程序中使用它
我将只关注 EDSDKLib
项目,因为这正是本文的主题:在 C# 中使用 EDSDK。
首先,我们来看看主要的类
CanonAPI
:主要负责 SDK 生命周期、已连接相机和 SDK 事件的类Camera
:用于与物理相机通信。设置
/获取
属性、拍照、下载数据等。CanonSDK
:此类包含所有本地对 Canon SDK DLL 的调用以及一些用于设置
/获取
值的辅助方法。STAThread:
一个辅助类,用于创建 STA 线程或在 STA 线程上执行代码ErrorHandler
:一个静态
类,提供检查 SDK 返回值/错误代码的方法
让我们更仔细地研究这些类的内部工作原理。
初始化和终止 SDK
初始化和终止是最简单的事情。当您启动程序时,创建一个新的 CanonAPI
类实例
public CanonAPI(bool useCallingThread)
{
try
{
//Ensure that only one caller at a time can increase the counter
lock (InitLock)
{
//If no instance exists yet, initialize everything
if (RefCount == 0)
{
if (useCallingThread)
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
throw new ThreadStateException("Calling thread must be in STA");
ErrorHandler.CheckError(this, CanonSDK.EdsInitializeSDK());
}
else
{
//Trying to trigger DllNotFoundException so it's not thrown
//in the event loop on a different thread:
CanonSDK.EdsRelease(IntPtr.Zero);
//Start the main thread where SDK will run on
MainThread = new ApiThread();
MainThread.Start();
//Initialize the SDK on the main thread
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsInitializeSDK()));
}
CanonSDK.InitializeVersion();
//Subscribe to the CameraAdded event
CameraAddedEvent = new SDKCameraAddedHandler(CanonAPI_CameraAddedEvent);
ErrorHandler.CheckError(this,
CanonSDK.EdsSetCameraAddedHandler(CameraAddedEvent, IntPtr.Zero));
_IsSDKInitialized = true;
}
RefCount++;
}
}
catch
{
IsDisposed = true;
if (MainThread?.IsRunning == true) MainThread.Shutdown();
throw;
}
}
确保您保留此类的实例,否则 GC 可能会收集它,SDK 也会随之终止(因为 CanonAPI
类的析构函数)。
当您关闭程序时,调用 Dispose
方法。公共 Dispose
方法依次调用此重载
protected virtual void Dispose(bool managed)
{
//Ensure that only one caller at a time can decrease the counter
lock (InitLock)
{
if (!IsDisposed)
{
//If it's the last instance, release everything
if (RefCount == 1)
{
_IsSDKInitialized = false;//Set beforehand because if an error happens,
//the SDK will be in an unstable state anyway
//Remove event handler for the CameraAdded event
ErrorCode err = CanonSDK.EdsSetCameraAddedHandler(null, IntPtr.Zero);
if (managed)
{
ErrorHandler.CheckError(this, err);
//Dispose all the connected cameras
CurrentCameras.ForEach(t => t.Dispose());
}
//Terminate the SDK
if (MainThread?.IsRunning == true)
err = MainThread.Invoke(() => { return CanonSDK.EdsTerminateSDK(); });
//Close the main thread
if (MainThread?.IsRunning == true) MainThread.Shutdown();
if (managed) ErrorHandler.CheckError(this, err);
}
RefCount--;
IsDisposed = true;
}
}
}
获取已连接的相机
现在 SDK 已经初始化,我们可以获取当前已连接相机的列表
public List<Camera> GetCameraList()
{
if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));
//Ensure that only one caller at a time can access the camera list
lock (CameraLock)
{
//Get a list of camera pointers
IEnumerable<IntPtr> ptrList = GetCameraPointerList();
List<Camera> camList = new List<Camera>();
//Find cameras that were connected before and add new ones
foreach (var ptr in ptrList)
{
var oldCam = CurrentCameras.FirstOrDefault(t => t.Reference == ptr);
if (oldCam != null && !oldCam.IsDisposed)
camList.Add(oldCam); //Pointer exists already so we reuse it
else camList.Add(new Camera(ptr)); //Pointer does not exists yet, so we add it
}
//Ensure that cameras not connected anymore are disposed properly
var oldCameras = CurrentCameras.Where(t => !ptrList.Any(u => u == t.Reference));
foreach (var cam in oldCameras) { if (!cam.IsDisposed) cam.Dispose(); }
CurrentCameras.Clear();
CurrentCameras.AddRange(camList);
return camList;
}
}
CanonAPI
类维护一个已连接相机的列表,以确保每个 SDK 相机指针仅与一个 Camera
类实例相关联。两个或多个使用相同 SDK 指针的 Camera
类实例在同时访问相机时可能会导致问题。
上面使用的子程序 GetCameraPointerList
获取指向 SDK 相机对象的指针列表
protected IEnumerable<IntPtr> GetCameraPointerList()
{
if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));
IntPtr camlist;
//Get camera list
ErrorHandler.CheckError(this, CanonSDK.EdsGetCameraList(out camlist));
//Get number of connected cameras
int camCount;
ErrorHandler.CheckError(this, CanonSDK.EdsGetChildCount(camlist, out camCount));
List<IntPtr> ptrList = new List<IntPtr>();
for (int i = 0; i < camCount; i++)
{
//Get camera pointer
IntPtr cptr;
ErrorHandler.CheckError(this, CanonSDK.EdsGetChildAtIndex(camlist, i, out cptr));
ptrList.Add(cptr);
}
//Release the list
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(camlist));
return ptrList;
}
打开和关闭与相机的会话
目前,我们完成了 CanonAPI
类的工作,可以开始使用 Camera
类并与其打开一个会话
public void OpenSession()
{
CheckState(false);
if (!SessionOpen)
{
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsOpenSession(CamRef));
//Check if Record is available
uint property;
_IsRecordAvailable = CanonSDK.GetPropertyData
(CamRef, PropertyID.Record, 0, out property) == ErrorCode.OK;
//Subscribe to events
SDKStateEvent += new SDKStateEventHandler(Camera_SDKStateEvent);
SDKPropertyEvent += new SDKPropertyEventHandler(Camera_SDKPropertyEvent);
SDKProgressCallbackEvent += new SDKProgressCallback
(Camera_SDKProgressCallbackEvent);
SDKObjectEvent += new SDKObjectEventHandler(Camera_SDKObjectEvent);
ErrorHandler.CheckError(this,
CanonSDK.EdsSetCameraStateEventHandler
(CamRef, StateEventID.All, SDKStateEvent, CamRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetObjectEventHandler
(CamRef, ObjectEventID.All, SDKObjectEvent, CamRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetPropertyEventHandler
(CamRef, PropertyEventID.All, SDKPropertyEvent, CamRef));
SessionOpen = true;
});
}
}
当您完成与此相机的操作时,可以通过这种方式关闭会话
public void CloseSession()
{
CheckState(false);
if (SessionOpen)
{
//Unsubscribe from all events
UnsubscribeEvents();
//If the live view is on, stop it
if (IsLiveViewOn)
{
KeepLVAlive = false;
LVThread.Join(5000);
}
MainThread.Invoke(() =>
{
//Close the session with the camera
ErrorHandler.CheckError(this, CanonSDK.EdsCloseSession(CamRef));
SessionOpen = false;
});
}
}
在相机连接到计算机时,您可以安全地多次打开和关闭会话。
当您完全完成与此相机的操作后,可以通过调用 Dispose
方法来释放它(与之前一样,它会在内部调用此重载)
protected virtual void Dispose(bool managed)
{
if (!IsDisposed)
{
//Unsubscribe from all events
UnsubscribeEvents();
//If the live view is on, stop it
if (IsLiveViewOn)
{
KeepLVAlive = false;
LVThread.Join();
}
IsLiveViewOn = false;
MainThread.Invoke(() =>
{
if (CanonAPI.IsSDKInitialized)
{
//If it's open, close the session
if (SessionOpen) CanonSDK.EdsCloseSession(CamRef);
//Release the camera
CanonSDK.EdsRelease(CamRef);
}
_IsDisposed = true;
});
//Shutdown the main camera thread
MainThread.Shutdown();
}
}
UnsubscribeEvents
只是一个小的辅助方法
private void UnsubscribeEvents()
{
SDKStateEvent -= Camera_SDKStateEvent;
SDKPropertyEvent -= Camera_SDKPropertyEvent;
SDKProgressCallbackEvent -= Camera_SDKProgressCallbackEvent;
SDKObjectEvent -= Camera_SDKObjectEvent;
if (CanonAPI.IsSDKInitialized)
{
MainThread.Invoke(() =>
{
//Clear callbacks from Canon SDK
CanonSDK.EdsSetCameraStateEventHandler(CamRef, StateEventID.All, null, CamRef);
CanonSDK.EdsSetObjectEventHandler(CamRef, ObjectEventID.All, null, CamRef);
CanonSDK.EdsSetPropertyEventHandler(CamRef, PropertyEventID.All, null, CamRef);
});
}
}
获取相机设置
对于带有 ID 的值,设置和获取相机设置可以非常简单,但对于结构
值则更困难。
这是普通int
值(如 Tv、Av 或 ISO)的获取方法
public int GetInt32Setting(PropertyID propID, int inParam = 0)
{
CheckState();
return MainThread.Invoke(() =>
{
int property;
ErrorHandler.CheckError(this, CanonSDK.GetPropertyData
(CamRef, propID, inParam, out property));
return property;
});
}
有多种方法可以获取不同类型的值,但它们基本上看起来都一样。重要的是,它们都调用 CanonSDK.GetPropertyData
方法,该方法处理所有详细信息。如果您对如何从托管代码获取非托管值感兴趣,请查看 CanonSDK
类中提到的方法及其重载。
设置相机设置
设置相机设置与获取它类似,但只有两种方法,因为我们可以以更通用的方式处理它,使用object
类型。
除string
之外的任何值都可以用此方法设置(例如,整数用于 Tv、Av 或 ISO)
public void SetSetting(PropertyID propID, object value, int inParam = 0)
{
CheckState();
MainThread.Invoke(() =>
{
int propsize;
DataType proptype;
ErrorHandler.CheckError(this,
CanonSDK.EdsGetPropertySize(CamRef, propID, inParam, out proptype, out propsize));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetPropertyData(CamRef, propID, inParam, propsize, value));
});
}
设置string
值
public void SetSetting(PropertyID propID, string value, int inParam = 0, int MAX = 32)
{
CheckState();
if (value == null) value = string.Empty;
if (value.Length > MAX - 1) value = value.Substring(0, MAX - 1);
byte[] propBytes = System.Text.Encoding.ASCII.GetBytes(value + '\0');
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsSetPropertyData(CamRef,
propID, inParam, propBytes.Length, propBytes));
});
}
String
长度可以通过 MAX
参数限制,因为某些相机属性只能具有特定长度。但大多数长度为 32 个字符。
获取可用设置列表
不同的相机具有不同的可用设置,镜头也只有一定的 Av 值范围。因此,您需要获取所有支持设置的列表。这仅适用于“AEModeSelect
”、“ISO
”、“Av
”、“Tv
”、“MeteringMode
”和“ExposureCompensation
”。此方法返回一个 CameraValue
数组。每个 CameraValue
都有一个整数值(这是 SDK 使用的 ID),一个string
值(可以用作标签,例如“ISO 100
”或“1/100
”)以及(如果适用)一个双精度值(可用于计算,例如 ISO 100 的值为 100,Tv 值为 1/100 的值为 0.01)。
public CameraValue[] GetSettingsList(PropertyID propId)
{
CheckState();
if (propId == PropertyID.AEModeSelect || propId == PropertyID.ISO ||
propId == PropertyID.Av || propId == PropertyID.Tv
|| propId == PropertyID.MeteringMode ||
propId == PropertyID.ExposureCompensation)
{
CameraValue[] vals = null;
PropertyDesc des = default(PropertyDesc);
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsGetPropertyDesc(CamRef, propId, out des)));
vals = new CameraValue[des.NumElements];
for (int i = 0; i < vals.Length; i++)
vals[i] = new CameraValue(des.PropDesc[i], propId);
return vals;
}
else throw new ArgumentException($"Method cannot be used with Property ID {propId}");
}
在该库中,还有五个对应类型的类支持此方法。这些类包含所有可能值的列表以及一些常用字段(如Auto
或Bulb
)。这些类称为AvValues
、TvValues
、ISOValues
、ExpCompValues
、AEModeValues
和MeteringModeValues
。这些类中的所有方法、属性和字段都是静态
的。
向相机发送命令
通过相机命令,您可以控制多项操作,例如,拍照、按下快门按钮或驱动镜头的对焦马达。
public void SendCommand(CameraCommand command, int inParam = 0)
{
CheckState();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}
其中一些命令将在以下主题中使用。
要更改相机的锁定状态或进入/退出直接传输模式,您可以使用相机状态命令
public void SendCommand(CameraCommand command, int inParam = 0)
{
CheckState();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}
拍照(普通模式和 B 门模式)
要使用当前设置拍照,请调用 TakePhoto
方法
public void TakePhoto()
{
CheckState();
SendCommand(CameraCommand.TakePicture);
}
或者,您可以使用 TakePhotoAsync
方法,该方法异步执行命令(在Threadpool
线程上,而不是在 .NET 4.5 任务上)。
或者,如果您的相机支持,您可以使用 TakePhoto快门
方法,该方法使用 PressShutterButton
命令而不是 TakePicture
命令
public void TakePhotoShutter()
{
CheckState();
SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Completely);
SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF);
}
与之前一样,您也可以使用 TakePhoto快门Async
方法进行异步执行。
要在 B 门模式下拍照,请调用 TakePhotoB门
方法
public void TakePhotoBulb(int bulbTime)
{
CheckState();
SendCommand(CameraCommand.BulbStart);
Thread.Sleep(bulbTime);
SendCommand(CameraCommand.BulbEnd);
}
请注意,在调用此方法之前,您必须将 Tv 值设置为Bulb
,否则将出错。反之亦然,对于 TakePhoto
方法,Tv 不能设置为Bulb
。
如果您想直接将拍摄的照片下载到您的计算机,请参阅下一个主题。
将拍摄的照片下载到计算机
要将拍摄的照片直接保存到计算机而不是相机内存中,请通过调用 SetSetting
方法进行设置
SetSetting(PropertyID.SaveTo, (int)SaveTo.Host); //SaveTo.Both would save the image
//to the camera AND the computer
大多数相机要求您在拍照前设置计算机上的剩余磁盘空间。如果这样做,大多数相机将假设没有剩余磁盘空间并返回错误。
您可以调用 SetCapacity
方法设置剩余磁盘空间(如果需要,您可以设置任意高的值)
public void SetCapacity(int bytesPerSector, int numberOfFreeClusters)
{
CheckState();
MainThread.Invoke(() =>
{
Capacity capacity = new Capacity(numberOfFreeClusters, bytesPerSector, true);
ErrorHandler.CheckError(this, CanonSDK.EdsSetCapacity(CamRef, capacity));
});
}
一旦您拍摄了照片,SDKObjectEvent
将会触发,inEvent
变量为 ObjectEventID.DirItemRequestTransfer
。为了方便起见,Camera
类有一个 DownloadReady
事件,该事件将触发并提供您所需的所有信息。
DownloadReady
事件有两个参数,一个是触发事件的 Camera
对象,第二个是 DownloadInfo
对象。DownloadInfo
对象提供有关文件的信息,并可传递给三个方法
DownloadFile
方法将图像下载到提供的目录中。要获取或设置文件名,您可以使用 DownloadInfo
对象的 FileName
属性。
public void DownloadFile(DownloadInfo Info, string directory)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
if (directory == null || string.IsNullOrEmpty(directory.Trim())) directory = ".";
string currentFile = Path.Combine(directory, Info.FileName);
if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
DownloadToFile(Info, currentFile);
}
不带目录参数的 DownloadFile
方法将图像下载到 System.IO.Stream
中,而不是将其保存到文件系统中。
public Stream DownloadFile(DownloadInfo Info)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
return DownloadToStream(Info);
}
使用 CancelDownload
方法,您可以取消并丢弃拍摄的图像
public void CancelDownload(DownloadInfo Info)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsDownloadCancel(Info.Reference));
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
});
}
两个 DownloadFile
方法都使用了这些子程序来执行实际工作
protected void DownloadToFile(DownloadInfo Info, string filepath)
{
using (var stream = new SDKStream
(filepath, FileCreateDisposition.CreateAlways, FileAccess.ReadWrite))
{
DownloadData(Info, stream.Reference);
}
}
和
protected Stream DownloadToStream(DownloadInfo Info)
{
SDKStream stream = new SDKStream(Info.Size64);
DownloadData(Info, stream.Reference);
stream.Position = 0;
return stream;
}
SDKStream
类是 SDK 流的包装器,因此它可以像普通的 System.IO.Stream
一样使用。
这就是实际的下载
protected void DownloadData(DownloadInfo Info, IntPtr stream)
{
MainThread.Invoke(() =>
{
try
{
//Set the progress callback
ErrorHandler.CheckError(this, CanonSDK.EdsSetProgressCallback
(stream, SDKProgressCallbackEvent, ProgressOption.Periodically, Info.Reference));
//Check which SDK version is used and download the data with the correct method
if (CanonSDK.IsVerGE34) ErrorHandler.CheckError
(this, CanonSDK.EdsDownload(Info.Reference, Info.Size64, stream));
else ErrorHandler.CheckError
(this, CanonSDK.EdsDownload(Info.Reference, Info.Size, stream));
}
finally
{
//Release all data
ErrorHandler.CheckError(this, CanonSDK.EdsDownloadComplete(Info.Reference));
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
}
});
}
启动和查看实时视图
实时视图是比较困难的事情之一,尤其是在需要高性能的情况下。首先,我们这样启动实时视图
public void StartLiveView()
{
CheckState();
if (!IsLiveViewOn) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.PC);
}
完成此操作后,SDKPropertyEvent
将触发,inPropertyID
变量为 PropertyID.Evf_OutputDevice
private ErrorCode Camera_SDKPropertyEvent
(PropertyEventID inEvent, PropertyID inPropertyID, int inParameter, IntPtr inContext)
{
ThreadPool.QueueUserWorkItem((state) =>
{
try
{
if (inPropertyID == PropertyID.Evf_OutputDevice ||
inPropertyID == PropertyID.Record)
{
lock (lvThreadLockObj)
{
EvfOutputDevice outDevice = GetEvf_OutputDevice();
Recording recordState = IsRecordAvailable ?
((Recording)GetInt32Setting(PropertyID.Record)) : Recording.Off;
if (outDevice == EvfOutputDevice.PC ||
(recordState == Recording.Ready &&
outDevice == EvfOutputDevice.Filming) ||
(useFilmingPcLv && recordState == Recording.On &&
(outDevice == EvfOutputDevice.Filming ||
outDevice == EvfOutputDevice.Camera)))
{
if (!KeepLVAlive)
{
KeepLVAlive = true;
LVThread = STAThread.CreateThread(DownloadEvf);
LVThread.Start();
}
}
else if (KeepLVAlive) { KeepLVAlive = false; }
}
}
}
catch (Exception ex)
{ if (!IsDisposed && !ErrorHandler.ReportError(this, ex)) throw; }
PropertyChanged?.Invoke(this, inEvent, inPropertyID, inParameter);
});
return ErrorCode.OK;
}
其中还有一些额外的检查,以确保实时视图在拍摄视频时也能正常工作。
并且 DownloadEvf
方法是
private void DownloadEvf()
{
if (IsLiveViewOn) return;
try
{
//Create variables
IntPtr evfImageRef = IntPtr.Zero;
ErrorCode err;
//Create stream
using (var stream = new SDKStream(0))
{
IsLiveViewOn = true;
//Run live view
while (KeepLVAlive)
{
//Get live view image
lock (STAThread.ExecLock)
{
err = CanonSDK.EdsCreateEvfImageRef(stream.Reference, out evfImageRef);
if (err == ErrorCode.OK) err =
CanonSDK.EdsDownloadEvfImage(CamRef, evfImageRef);
}
//Check for errors
if (err == ErrorCode.OBJECT_NOTREADY) { continue; }
else if (err != ErrorCode.OK) { ErrorHandler.CheckError(err); continue; }
//Release current evf image
CanonSDK.EdsRelease(evfImageRef);
//Set stream position back to zero
stream.Position = 0;
//Update live view
LiveViewUpdated?.Invoke(this, stream);
}
}
}
catch (Exception ex) { if (ex is ThreadAbortException ||
!ErrorHandler.ReportError(this, ex)) throw; }
finally
{
IsLiveViewOn = false;
ThreadPool.QueueUserWorkItem((state) => LiveViewStopped?.Invoke(this));
}
}
要停止实时视图,只需调用 StopLiveView
方法。这将再次触发 SDKPropertyEvent
,其中 KeepLVAlive
变量设置为false
,实时视图循环将停止。使用 LVOff
参数,您可以设置是完全关闭实时视图还是在相机上显示它。(某些旧相机可能会一直关闭。)
public void StopLiveView(bool LVOff = true)
{
CheckState();
if (LVOff) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Off);
else SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Camera);
}
录制视频
较新的相机内置了录制视频的可能性。要使用此功能,您必须首先将相机设置为视频录制模式(否则将无法工作!)。通常,您需要切换某个按钮、旋钮或拨盘。完成后,您可以调用启动方法
public void StartFilming(bool PCLiveview)
{
CheckState();
Recording state = (Recording)GetInt32Setting(PropertyID.Record);
if (state != Recording.On)
{
if (state != Recording.Ready) throw new InvalidOperationException
("The camera is not ready to film. The Record property has to be Recording.Ready");
useFilmingPcLv = PCLiveview;
//When recording videos, it has to be saved on the camera internal memory
SetSetting(PropertyID.SaveTo, (int)SaveTo.Camera);
//Start the video recording
SetSetting(PropertyID.Record, (int)Recording.On);
}
}
请注意,SaveTo
属性设置为Camera
。这是 SDK 的要求,据推测是因为传输到计算机的数据速度太慢。
尽管如此,您仍然可以在停止拍摄后通过将 StopFilming
方法的 saveFilm
参数设置为true
来下载已完成的影片
public void StopFilming(bool saveFilm, bool stopLiveView)
{
CheckState();
Recording state = (Recording)GetInt32Setting(PropertyID.Record);
if (state == Recording.On)
{
this.saveFilm = saveFilm;
//Stop video recording
SetSetting(PropertyID.Record, (int)Recording.Off);
useFilmingPcLv = false;
if (IsLiveViewOn && stopLiveView) StopLiveView(false);
}
}
内部,它会检查 SDKObjectEvent
,该事件将触发,inEvent
为 ObjectEventID.DirItemCreated
。与下载照片一样,DownloadReady
事件将触发,您可以使用 DownloadFile
方法之一下载影片。
锁定/解锁相机 UI
为了防止用户更改物理相机上的设置,或者允许更改,您可以像这样锁定或解锁相机 UI
public void UILock(bool lockState)
{
if (lockState) SendStatusCommand(CameraStatusCommand.UILock);
else SendStatusCommand(CameraStatusCommand.UIUnLock);
}
控制镜头的对焦
互联网上经常被问到的一个问题是如何控制相机的对焦。如果您知道方法,其实很简单。最重要的是,相机必须处于实时视图模式,并且镜头必须设置为 AF。然后,您只需调用 SendCommand
并将 DriveLensEvf
作为命令类型
SendCommand(CameraCommand.DriveLensEvf, (int)DriveLens.Near2);
要控制距离和方向,您需要相应地设置 DriveLens
枚举
。Near 表示对焦更近,Far 表示对焦更远,1 表示小步,2 表示中等步,3 表示大步。
相机上的文件和文件夹
有几种方法可以处理相机的文件系统
- 使用
GetAllVolumes
获取所有相机卷(例如 CF 或 SD 卡) - 使用
GetAllEntries
获取相机上的所有卷、文件和文件夹 - 使用
GetAllImages
获取相机上的所有图像 - 使用
FormatVolume
格式化卷 - 使用
DownloadFiles
将一个或多个文件下载到目录中 - 使用
DeleteFiles
删除相机上的一个或多个文件
我不会在这里详细介绍,但请随时查看这些方法的源代码,以了解它们的确切工作方式。
获取图像缩略图
有时,获取图像的缩略图很有用,无论它是 RAW 还是 JPG。为此,您可以调用 CanonAPI
类的 GetFileThumb
方法。
public Bitmap GetFileThumb(string filepath)
{
//create a file stream to given file
using (var stream = new SDKStream
(filepath, FileCreateDisposition.OpenExisting, FileAccess.Read))
{
//Create a thumbnail Bitmap from the stream
return GetImage(stream.Reference, ImageSource.Thumbnail);
}
}
而更通用的方法 GetImage
定义如下
protected Bitmap GetImage(IntPtr imgStream, ImageSource imageSource)
{
IntPtr imgRef = IntPtr.Zero;
IntPtr streamPointer = IntPtr.Zero;
ImageInfo imageInfo;
try
{
//create reference and get image info
ErrorHandler.CheckError(this,
CanonSDK.EdsCreateImageRef(imgStream, out imgRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsGetImageInfo(imgRef, imageSource, out imageInfo));
Size outputSize = new Size();
outputSize.Width = imageInfo.EffectiveRect.Width;
outputSize.Height = imageInfo.EffectiveRect.Height;
//calculate amount of data
int datalength = outputSize.Height * outputSize.Width * 3;
//create buffer that stores the image
byte[] buffer = new byte[datalength];
//create a stream to the buffer
using (var stream = new SDKStream(buffer))
{
//load image into the buffer
ErrorHandler.CheckError(this, CanonSDK.EdsGetImage
(imgRef, imageSource, TargetImageType.RGB,
imageInfo.EffectiveRect, outputSize, stream.Reference));
//make BGR from RGB (System.Drawing (i.e. GDI+) uses BGR)
unsafe
{
byte tmp;
fixed (byte* pix = buffer)
{
for (long i = 0; i < datalength; i += 3)
{
tmp = pix[i]; //Save B value
pix[i] = pix[i + 2]; //Set B value with R value
pix[i + 2] = tmp; //Set R value with B value
}
}
}
//Get pointer to stream data
ErrorHandler.CheckError(this,
CanonSDK.EdsGetPointer(stream.Reference, out streamPointer));
//Create bitmap with the data in the buffer
return new Bitmap(outputSize.Width, outputSize.Height,
datalength, PixelFormat.Format24bppRgb, streamPointer);
}
}
finally
{
//Release all data
if (imgStream != IntPtr.Zero) ErrorHandler.CheckError
(this, CanonSDK.EdsRelease(imgStream));
if (imgRef != IntPtr.Zero) ErrorHandler.CheckError
(this, CanonSDK.EdsRelease(imgRef));
}
}
关于方法的说明
我没有在这里添加代码中使用的所有方法。如果有些地方不太清楚,请下载源代码。如果您在此之后仍有疑问,请随时问我。 :)
关于线程的说明
Canon SDK 要求使用单线程单元(=STA)中的线程,这使得事情有些棘手。幸运的是,我添加了 STAThread
类,它处理这种线程模型的设置和问题。CanonAPI
类有一个用于初始化和终止 SDK 以及获取所有 SDK 事件的线程。每个 Camera
实例都有一个用于在上面执行所有命令的线程。为了确保没有任何东西同时执行,所有命令都用锁包围(以 ExecLock
字段作为锁对象)。如果两个命令同时执行,SDK 在大多数情况下会完全挂起。CanonAPI
和 Camera
提供的所有方法都可以安全执行,但如果您想调用 CanonSDK
类的方法,您必须确保在正确的线程上执行它(通过调用 STAThread
类的 Invoke
方法)。如果您在 SDK 挂起方面遇到任何问题,请告诉我。
使用 GUI
此项目包括一个 Windows Forms UI 和一个 WPF UI。它们的工作方式相同,旨在向您展示如何在实际程序中使用上述代码。
注意:这些 GUI 不适用于生产环境,仅作为示例供您入门!
插入您的相机并在列表中选择。点击“打开会话”并开始使用相机。
在下拉菜单中选择值以在相机中设置它们。
使用“StartLV”按钮启动实时视图,使用带有箭头的按钮可以控制对焦(仅当镜头处于 AF 模式且实时视图正在运行时才有效)。
关注点
此代码已在以下设备上测试过
- EOS 5D Mark III
- EOS 7D
- EOS 40D
- EOS 60D
- EOS 700D
- EOS 600D
- EOS 550D
- EOS 500D
- EOS 450D
- EOS 100D/Rebel SL1
- EOS 1200D/Rebel T5
- EOS 1100D/Rebel T3
- 以及其他一些型号
如果您在其他型号上进行过测试,请告诉我,以便我将其添加到此列表中。
如果您发现了 bug、有改进建议或新的代码片段,我很高兴收到您的来信!
历史
- 2013 年 11 月 - 初始版本
- 2013 年 11 月 - 修复了在查看
LiveView
和拍照同时进行时的 bug - 2013 年 12 月 - 添加了
GetStringProperty
和GetStructProperty
,并添加了视频录制 - 2014 年 1 月 - 添加了
SetStringProperty
和SetStructProperty
,并对 UI 进行了小的更改 - 2014 年 1 月 - 添加了多个新方法,并使代码更加安全
- 2014 年 6 月 - 更改了一些方法以提高安全性,并对代码和 UI 进行了少量修改
- 2014 年 10 月 - 修复了一些 bug,修改了 Winforms UI 并添加了 WPF UI
- 2014 年 11 月 - 修复了实时视图运行时调用相机命令时的死锁问题
- 2015 年 4 月 - 修复了相机在拍摄前后挂起的 bug。修复了损坏的链接
- 2015 年 5 月 - 相机命令现在在 STA 线程上正确执行。其他小修复
- 2015 年 8 月 - UI:修复了
CameraAdded
+Shutdown
bug;添加了基本错误处理。Lib:小的修复
- 2016 年 3 月(1.0.0) - 项目完全修订。现在它使用了与商业库类似的架构。
- 2016 年 3 月(1.0.1) - 修复了
StopFilming
方法中的错误检查 - 2016 年 4 月(1.1.0) - 多项 bug 修复、改进以及对 Canon SDK 3.4 的支持
- 2016 年 4 月(1.1.1) - 修复了
STAThread.Invoke
方法中的不正确锁定(感谢 Dave) - 2016 年 5 月(1.1.2) - 各种小修复(感谢 elgarf)并添加了
LiveViewStopped
事件