Azure 中的图像:上传、深度缩放查看、标记和注释






4.96/5 (24投票s)
本文介绍基于 Azure 的软件,用于图像上传、使用 Silverlight MultiScaleImage 控件进行查看、标记和注释。浏览器执行这些操作,无需在用户计算机/设备上进行任何额外安装(查看浏览器应支持 Silverlight)。

引言
云计算的一个明显应用是为大量数据(尤其是图像)提供存储、处理和快速访问。在处理了一个非云图像访问系统一段时间后,我想实现一个简单的软件,允许将图像上传到 Azure Blob Storage,然后轻松查看它们。除了快速访问存储的图像外,允许用户标记图像中的特定区域、对其进行注释并将这些信息存储在 Azure Blob Storage 中以供将来使用也很重要。最好是仅通过浏览器提供上传、查看和标记功能,并在客户端计算机上进行最少(最好为零)的安装。牢记这一目标,我浏览了网络以查找相关信息和示例。我发现了很多涵盖该问题某些方面的文章,主要与图像查看有关。我没有找到一个完整的代码示例来解决上述问题。我也没有找到一个合适的图像标记实现。
本文提供的代码示例能够
- 将图像从本地计算机/设备上传到 Azure Blob Storage,
- 即时下载存储的图像,并能够以几乎零延迟的方式缩放、调整大小和移动它,
- 标记图像中的特定区域并对其进行注释,以及
- 将标记和注释元数据与图像一起保存/加载到/从 Azure Blob Storage。
我的代码基于其他人的先前工作(请参阅文章末尾的参考文献),特别是 Joerg Lang [9] 在 CodeProject 上的文章。
背景
从用户的角度来看,该软件提供了通过适当的网页将图像文件(当前查看器支持*jpg*和*png*格式)从任何计算机或设备上传到 Windows Azure Blob Storage 的可能性,然后使用另一个网页查看图像,在图像上标记特定区域(目前仅限矩形区域),编写关于标记区域的描述(注释、备注)并保存这些区域及其描述。
可以理解的是,文件上传应从任何浏览器都可以读取的简单 HTML 页面进行。这允许用户几乎从任何计算机和移动设备上传图像。WCF RESTful 服务ImagingSvc
在服务器(Azure 云)端充当对应端。此服务负责提供上传图像到 Azure 的 HTML 页面。对于图像查看,采用了 Microsoft Silverlight 的MultiScaleImage
控件。因此,查看浏览器应支持 Silverlight。目前大多数浏览器都满足此要求(可能除了移动设备上的浏览器)。MultiScaleImage
控件基于深度缩放 (DZ) 技术 [1-6]。Wikipedia 对 DZ 的描述如下 [1]
深度缩放是微软提供的用于图像查看应用程序的开源技术的实现。它允许用户在大型、高分辨率图像或大型图像集合中进行平移和缩放。它通过仅下载正在查看的区域以及/或仅以显示的分辨率来减少初始加载所需的时间。当用户平移(或缩放)到后续区域时,会下载这些区域;动画用于隐藏过渡过程中的任何卡顿。
DZ 需要从原始图像构建的瓦片图像金字塔 [2, 3]。图像金字塔的大小会超过原始图像的大小(根据一些估计,平均会增加 1.3 倍)。但这种技术允许快速平滑地下载图像以供查看。Microsoft 提供了特殊的 Deep Zoom Composer [7] 工具来生成瓦片图像金字塔。但使用此工具无助于我们的任务,因为首先需要安装该工具,其次,会大大增加要上传的数据量。显然,我们必须提供一个更适合我们目的的瓦片金字塔生成工具。
需要解决的另一个问题是,MultiScaleImage
控件虽然是出色的图像查看工具,但不提供标记和注释任务的支持。这意味着这些重要功能需要“手动”实现。在实现功能方面,不仅要为选定的区域绘制边框,还要在各种视觉变换期间同步这些边框与图像本身的移动。
设计
选择了 CodeProject 文章 [8, 9] 和博客文章 [10] 作为设计的起点。前者提供了图像瓦片金字塔生成的算法和代码。所需更改是将图像存储为 Azure Blob Storage 中的 blob,而不是数据库。后者包含有关设置 Azure Blob Storage 容器权限的有用提示。在开发过程中,我还使用了 Cloudberry Explorer 工具来检查和管理 Azure Blob Storage。
使用 VS2010 向导创建了一个 Web Role WCF 应用程序ImagingService
。应用程序中添加了两个 WCF 服务组件ImagingSvc
和SlSvc
,以及一个 Silverlight 组件MsiHelperLib
和SlImage
。VS2010 解决方案结构如下图所示

RESTful WCF 服务ImagingSvc
提供了一种从客户端计算机/设备上传图像、将其处理为 DZ 图像瓦片金字塔并将其存储到 Azure Blob Storage 的方法。此服务具有webHttpBinding
。SlSvc
WCF 服务旨在进行 Azure 应用程序内部通信,以为基于 Silverlight 的组件SlImage
提供数据。该服务具有basicHttpBinding
和相对地址,以简化SlImage
组件对其的访问。下面展示了两个 WCF 服务的接口和配置
[ServiceContract]
interface IImagingSvc
{
[OperationContract]
[WebGet(UriTemplate = "/{data}", BodyStyle = WebMessageBodyStyle.Bare)]
Message Init(string data);
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare)]
Message Upload(Stream stream);
}
[ServiceContract]
public interface ISlSvc
{
[OperationContract]
string BlobContainerUri();
[OperationContract]
string[] BlobsInContainer();
[OperationContract]
void SaveEditing(string blobId, string xmlEditing);
[OperationContract]
string LoadEditing(string blobId);
}
<configuration>
<system.diagnostics>
<trace>
<listeners>
<add type="Microsoft.WindowsAzure.Diagnostics.
DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.
Diagnostics, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
name="AzureDiagnostics">
<filter type="" />
</add>
</listeners>
</trace>
</system.diagnostics>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="2147483647"/>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<webHttpBinding>
<binding name="StreamWebHttpBinding"
maxBufferPoolSize="1000"
maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"
closeTimeout="00:25:00" openTimeout="00:01:00"
receiveTimeout="01:00:00" sendTimeout="01:00:00"
transferMode="Streamed" bypassProxyOnLocal="false">
<readerQuotas maxDepth="333333" maxStringContentLength="333333"
maxArrayLength="333333"
maxBytesPerRead="333333" maxNameTableCharCount="333333" />
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="RestBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="LargeUploadBehavior">
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ImagingService.ImagingSvc"
behaviorConfiguration="LargeUploadBehavior" >
<endpoint address=""
contract="ImagingService.IImagingSvc"
binding="webHttpBinding"
bindingConfiguration="StreamWebHttpBinding"
behaviorConfiguration="RestBehavior" />
</service>
<service name="ImagingService.SlSvc" >
<endpoint address=""
contract="ImagingService.ISlSvc" binding="basicHttpBinding" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
MsiHelperLib.MsiHelper
类型作为MultiScaleImage
的包装器,管理该控件并将其链接到主SlImage
组件。MsiHelper
类的构造函数接收主 GUI 组件(在本例中为SlImage
)中定义的三个 GUI 控件,即MultiScaleImage
、canvas 和DataGrid
作为参数,并协调它们的行为和事件。MsiHelper
负责处理MultiScaleImage
的主要活动,例如用于图像缩放和移动的鼠标按钮和滚轮事件,以及处理标记、注释和相关数据的序列化。其某些方法被设为virtual protected
,以便在后续开发中通过派生类轻松添加新功能。MouseWheelHelper
类管理鼠标滚轮,而DescriptionChildWindow
提供描述(注释)对话框。
SlImage
是用于查看先前上传和处理的图像的主 Web GUI 组件。它充当SlSvc
WCF 服务的客户端。SlImage
定义了MultiScaleImage
、canvas 和DataGrid
控件,并使用MsiHelperLib
组件来管理它们。其MainPage
类型处理由 GUI 控件引起的事件,并调用MsiHelperLib.MsiHelper
类型的相应方法。SlImage
通过专用的*SlImage.html*文件公开访问。由于是基于 Silverlight 的,对象SlImage
无法直接与 Azure 对象通信。为了进行通信(例如,获取存储的图像 blob 列表),*SlImage*依赖于SlSvc
*WCF 服务。VS2010 允许开发人员轻松地在SlSvc
*WCF 服务上创建服务引用并生成相应的类SlImage.SlSvc_ServiceReference.SlSvcClient
。SlImage
使用此类的实例(代理)与主应用程序通信。*标签<configuration> <system.servicemodel> <bindings>
<system.serviceModel>
SlImage
的配置文件*ServiceReferences.ClientConfig*如下所示
<configuration><system.servicemodel><bindings><system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ISlSvc"
maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="../SlSvc.svc"
binding="basicHttpBinding" contract="SlSvc_ServiceReference.ISlSvc" />
</client>
</system.serviceModel><basichttpbinding>
<binding name="BasicHttpBinding_ISlSvc" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
</binding></basichttpbinding></bindings>
</system.servicemodel></configuration>
标记
如上所述,本文的代码允许用户在图像中标记选定的感兴趣区域。System.Windows.Controls.Canvas
类型的实例用于绘制标记边框(当前版本仅支持矩形区域,但MsiHelper
类型的基础结构及其虚拟方法有助于在派生类中处理具有更复杂边界的区域)。在画布上绘制标记矩形的实现相对简单。结果表明,更复杂的问题是与图像本身在各种视觉变换期间同步此标记的行为。以下代码显示了确保同步视觉演变的几何变换
public CompositeTransform MsiTransform
{
get
{
CompositeTransform compositeTransform = new CompositeTransform();
compositeTransform.TranslateX = msi.Margin.Left -
msi.ActualWidth / msi.ViewportWidth * msi.ViewportOrigin.X;
compositeTransform.TranslateY = msi.Margin.Top -
msi.ActualWidth / msi.ViewportWidth * msi.ViewportOrigin.Y;
compositeTransform.ScaleX = 1 / msi.ViewportWidth;
compositeTransform.ScaleY = 1 / msi.ViewportWidth;
return compositeTransform;
}
}
private Point TransformGeometryPoint(Point pt)
{
CompositeTransform ct = MsiTransform;
return new Point((pt.X - ct.TranslateX) / ct.ScaleX,
(pt.Y - ct.TranslateY) / ct.ScaleY);
}
private double TransformGeometryLineLength(double len)
{
return len * msi.ViewportWidth;
}
标记功能的另一个支持也意味着能够保存(序列化)然后恢复(反序列化)标记信息。MsiHelper
类型对此负责。标记相关数据以 XML 格式存储到 Azure Blob Storage。序列化使用几个辅助类型进行,即PathTextualInfo
、GeometryInfo
、Saved
和ItemToSave
。以下代码显示了用于反序列化先前存储的标记数据的几何变换
public void ProcessLoadedEditInfo(string xmlEditInfo)
{
if (!string.IsNullOrEmpty(xmlEditInfo))
{
XmlSerializer serializer = new XmlSerializer(typeof(Saved));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xmlEditInfo));
saved = serializer.Deserialize(ms) as Saved;
ms.Close();
allowChange = true;
restoreActualWidthFactor = msi.ActualWidth / saved.msiActualWidth;
msi.ViewportWidth = saved.msiViewportWidth * restoreActualWidthFactor;
msi.ViewportOrigin = saved.msiViewportOrigin;
msi.Margin = saved.msiMargin;
}
}
代码示例
本文的代码可以在本地 Azure 开发环境中进行测试。您应该“以管理员身份”启动 VS2010,在其中加载AzureDz.sln解决方案,然后构建并运行AzureDz
项目。要上传图像文件,您应该将浏览器导航到http://127.0.0.1:81/ImagingSvc.svc/Init,然后从该页面执行图像文件上传。要查看上传的图像,请将浏览器导航到http://127.0.0.1:81/SlImage.html。要在 Azure 本地开发环境中运行解决方案,请将ServiceConfiguration.cscfg文件中的StorageAccountName
和StorageAccountKey
参数的值留空。
要从 Azure 云运行示例,您首先需要创建一个存储帐户,并将其名称和主访问密钥分配给ServiceConfiguration.cscfg文件中的StorageAccountName
和StorageAccountKey
参数。然后,您需要在云中创建一个托管服务*占位符*,构建发布版本,将其发布并部署到新创建的托管服务。图像上传可以从http://place-holder.cloudapp.net/ImagingSvc.svc/Init页面执行。可以通过导航到http://place-holder.cloudapp.net/SlImage.html来查看图像。
工作流程如下。共享图像的客户端,可以在任何支持浏览器的设备上,将其浏览器导航到http://.../ImagingSvc.svc/Init。调用ImagingSvc
RESTful WCF 服务的Init()
方法,并作为响应返回一个简单的 HTML 页面到浏览器。此 HTML 页面允许客户端上传带有 blob 令牌(默认情况下是图像文件名)的图像,并将其发送到 WCF 服务。图像通过ImagingSvc
WCF 服务的Upload()
方法作为流上传。此方法还将接收到的图像处理为 DZ 图像瓦片金字塔并将其放入 Azure Blob Storage。现在,可以使用任何支持 Silverlight 的浏览器查看和标记图像。要查看图像,客户端应将其浏览器导航到http://.../SlImage.html,然后从Blob Token组合框中选择图像。所选图像由MultiScaleImage
Silverlight 控件显示。其Source
属性(在MsiHelper
的Source
属性中)被设置为位于 Azure Blob Storage 中的图像金字塔 XML blob。
用户通过浏览器中的 HTMLinput
标签(类型为“file”)操作来上传图像文件。这会导致包含图像字节表示的流式 HTTP POST 请求。此请求由ImagingSvc
WCF 服务的Upload()
方法接收。此类请求及其结构的示例显示在下表中
模式示例 |
描述 |
-----------------------------7db3c97d0958 | 分隔符 |
\r\n | CRLF - 表示分隔符结束 |
Content-Disposition: form-data; name=\"SrcPath\"; filename=\"Picture.jpg\"\r\nContent-Type: image/pjpeg |
包含源文件名和内容类型的字段 |
\r\n\r\n | 双 CRLF - 表示图像字节开始 |
图像字节 | 图像的字节数组 |
\r\n | CRLF - 表示图像字节结束 |
-----------------------------7db3c97d0958 | 分隔符 |
\r\nContent-Disposition: form-data; name=\"DstPath\"\r\n\r\nPictureBlob.jpg\r\n | 包含 blob 令牌的字段 |
-----------------------------7db3c97d0958--\r\n | 分隔符和最终模式 |
对请求进行解析,以提取图像的字节数组以及 blob 令牌和数据类型。目前,此解析是通过StaticHttpParser
类的*方法*“手动”进行的(我几乎肯定有一个标准的、现成类型的方法来解析此请求,但我未能快速找到它。也许Upload()
方法应该使用System.ServiceModel.Channels.Message
类型的参数……)。
我们基于 Silverlight 的客户端应用程序支持标记功能。用户可以通过选中*Edit*复选框(下图所示)将 GUI 切换到编辑模式。

在编辑模式下,鼠标操作会进行标记,而不是移动或缩放图像。图像上方的简单控制面板允许用户选择标记线的粗细、线的颜色和填充颜色以及不透明度参数。然后,通过按住鼠标左键并拖动鼠标,用户定义所选的矩形标记区域。选择完成后,将出现*Highlighted Area Description*对话框(类型为DescriptionChildWindow
),提示用户为选定区域定义图层、标题和描述。或者,通过按*Cancel*按钮,用户可以取消最新的选定区域的标记。在编辑模式下,在选定区域内单击鼠标左键会删除该选定区域。下图中展示了编辑结果,三个选定的对象。这些对象分为两个图层:Characters(Alice,橙色边界,透明填充;White Rabbit,绿色边界,红色填充)和 Things(White Rabbit's Watch,蓝色边界,白色填充)。对象具有不同的不透明度。编辑后,可以通过取消选中*Edit*复选框将客户端切换回查看模式。在查看模式下,当前选定的标记会通过“闪烁”进行突出显示。通过在DataGrid
对象的 Visible 列中选中/取消选中复选框,可以打开/关闭标记。
讨论
本文提供的代码已在真实的 Azure 云上进行了测试,使用了最大的 14MB 图像。上传后,浏览器中会显示相应的消息(有时对于大图像,上传后可能会收到一些异常的 HTML 响应,但通常上传是成功的)。在本地开发环境中,在准备图像金字塔时可能会发生*OutOfMemory*异常。异常源位于*try-catch*块中,并且相应的上传后消息会告知用户并非所有图像图层都能正确查看。至于标记编辑,目前相应的功能相当有限。因此,算法改进和编辑功能还有很大的提升空间。
如上所述,使用 DZ 技术比仅存储原始图像需要更多的存储空间。如果存储空间是一个问题,那么可以根据需要为存储的图像生成 DZ 图像金字塔,或者应该采用一些缓存机制(可能结合适当的调度)。例如,如果已知医生明天将检查某些患者的 X 射线图像,那么可以在检查前一晚为这些图像生成 DZ 图像金字塔,并在检查后销毁它们。
请注意,本文附带的代码不是一个完整的产品,而是概念的说明。因此,为了简单起见,一些在现实世界中重要的功能被故意省略了。这些功能是
- 图像上传、查看和注释的权限
- 支持不同用户或用户组的不同标记
- 多个用户同时标记同一图像的策略
- 标记区域的 Z 顺序等,列表不详尽
结论
本文介绍了一个工具,可以将图像文件上传到 Azure 云,将图像处理为深度缩放图像瓦片金字塔,将金字塔图像作为 blob 存储到 Azure Blob Storage,使用 Microsoft Silverlight 提供的MultiScaleImage
控件查看图像,以及标记和注释图像中的兴趣区域。所有上述操作都仅通过浏览器完成,无需在用户计算机上进行任何额外安装(但查看浏览器应支持 Silverlight)。
参考文献
[ 1] 深度缩放。维基百科。
[ 2] Daniel Gasienica。 深度缩放内部 - 第一部分:多尺度成像。
[ 3] Daniel Gasienica。 深度缩放内部 - 第二部分:数学分析。
[ 4] 构建 Silverlight 深度缩放应用程序。
[ 5] Jaime Rodriguez。 深度缩放入门(解释和编码)。.
[ 6] Sacha Barber。 深度缩放。 CodeProject。
[ 7] Microsoft Deep Zoom Composer。
[ 8] Berend Engelbrecht。 从多页 TIFF 生成 Silverlight 2 深度缩放图像集合。 CodeProject。
[ 9] Joerg Lang。 Silverlight 数据库深度缩放。 CodeProject。
[10] 如何将我的深度缩放图像放入 Azure?
历史
初始版本支持将图像从本地计算机/设备上传到 Azure Blob Storage,并即时查看存储的图像,同时能够缩放、调整大小和移动。在第二个版本中,添加了标记、编辑和相关数据的序列化功能。