从隔离存储中加载大量图像时出现的OutOfMemory异常

4
编辑:我一直收到未处理的OutOfMemoryException异常,我认为这是因为我保存图像到隔离存储的方式,我认为这是我可以解决问题的地方,如何在保存之前减小图像的大小?(添加了保存图像的代码)
我从隔离存储中打开图像,有时会有超过100个图像,并且我想遍历所有这些图像,但当故事板中加载了大约100到150个图像时,我会出现OutOfMemory Exception。如何处理此异常?我已经降低了图像的分辨率。如何避免我的应用程序崩溃?
我在这一行代码上遇到了异常: image.SetSource(isStoreTwo.OpenFile(projectFolder + "\\MyImage" + i + ".jpg", FileMode.Open, FileAccess.Read));//images from isolated storage 下面是我的代码:
private void OnLoaded(object sender, RoutedEventArgs e)
    {


        IsolatedStorageFile isStoreTwo = IsolatedStorageFile.GetUserStoreForApplication();



        try
        {
            storyboard = new Storyboard
            {
                //RepeatBehavior = RepeatBehavior.Forever
            };

            var animation = new ObjectAnimationUsingKeyFrames();

            Storyboard.SetTarget(animation, projectImage);
            Storyboard.SetTargetProperty(animation, new PropertyPath("Source"));


            storyboard.Children.Add(animation);
            for (int i = 1; i <= savedCounter; i++)
            {
                BitmapImage image = new BitmapImage();

                image.SetSource(isStoreTwo.OpenFile(projectFolder + "\\MyImage" + i + ".jpg", FileMode.Open, FileAccess.Read));//images from isolated storage

                var keyframe = new DiscreteObjectKeyFrame
                {

                    KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(100 * i)),
                    Value = image
                };

                animation.KeyFrames.Add(keyframe);
            }
        }
        catch (OutOfMemoryException exc)
        {

            //throw;

        }



        Resources.Add("ProjectStoryBoard", storyboard);
        storyboard.Begin();
    }

编辑 这是我将图像保存到隔离存储的方法,我认为这里可以解决我的问题,当我将其保存到隔离存储时,如何减小图像的大小?

    void cam_CaptureImageAvailable(object sender, Microsoft.Devices.ContentReadyEventArgs e)
    {

        string fileName = folderName+"\\MyImage" + savedCounter + ".jpg";

        try
        {  

            // Save picture to the library camera roll.
            //library.SavePictureToCameraRoll(fileName, e.ImageStream);



            // Set the position of the stream back to start
            e.ImageStream.Seek(0, SeekOrigin.Begin);

            // Save picture as JPEG to isolated storage.
            using (IsolatedStorageFile isStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream targetStream = isStore.OpenFile(fileName, FileMode.Create, FileAccess.Write))
                {

                    // Initialize the buffer for 4KB disk pages.
                    byte[] readBuffer = new byte[4096];
                    int bytesRead = -1;

                    // Copy the image to isolated storage. 
                    while ((bytesRead = e.ImageStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
                    {
                        targetStream.Write(readBuffer, 0, bytesRead);
                    }

                }

            }




        }
        finally
        {
            // Close image stream
            e.ImageStream.Close();
        }

    }

I would appreciate if you could help me thanks.

7个回答

7
无论您的图像在磁盘上有多大,当您将它们加载到内存中时,它们将被解压缩。图像所需的内存将约为 (stride * height)stridewidth * bitsPerPixel)/8,然后向上舍入到 4 字节的下一个倍数。因此,一个尺寸为 1024x768,每像素 24 位的图像将占用约 2.25 MB 的空间。
您应该计算出您的图像未压缩时的大小,并使用该数字来确定内存需求。

我的图像大小为640x480,色深度为24位,因此不到1MB,但我要打开超过100次的隔离存储。 - M_K
一张每像素24位的640x480分辨率的图像大小约为900千字节。100张这样的图片将占用87兆字节,至少如此。还会有其他一些开销。你知道在出现内存不足异常之前添加了多少张图片吗? - Jim Mischel
是的,我刚刚检查了三次计数器,它的值都是107。 - M_K
未压缩的图像更可能以每像素32位的格式存储。在这种情况下,107张图像将占用>125MB的空间。任务管理器对进程使用的内存有何说法? - Denis
我不知道如何使用任务管理器,我认为如果我将图像的大小减小到100kb或其他大小,那么它就可以停止内存耗尽。当我保存这些图像时,如何减小它们的大小?我已经添加了上面的代码。 - M_K

6
您之所以会收到OutOfMemory Exception错误,是因为您同时将所有图像存储在内存中以创建StoryBoard。我认为您无法克服图像需要在屏幕上显示的未压缩位图大小。
因此,为了解决这个问题,我们必须考虑您的目标,而不是尝试修复错误。如果您的目标是每隔X毫秒显示一个新图像,那么您有几个选择。
1. 继续使用StoryBoards,但使用OnCompleted事件链接它们。这样,您不必一次性创建它们,而只需生成下一个即可。但如果您每100毫秒更改图像,则可能不够快。
2. 如我在这里提到的那样,使用CompositionTarget.Rendering。如果您仅预加载下一个图像(而不是像当前解决方案一样全部预加载),则这可能需要最少的内存。但您需要手动检查经过的时间。
3. 重新考虑您正在做的事情。如果您说明您要做什么,人们可能会有更多的替代方案。

同意。您不应该一次加载100张图片。只需加载需要显示的图片,如果存在延迟问题,则可以预加载一些图片。 - Kevin Gosse
2
@M_K 好的,请告诉我进展如何。另一个选择可能是使用计时器,比如DispatcherTimer来更改图像。这可能比链接StoryBoards或使用CompositionTarget.Rendering更简单。这里有一个非常相似的例子:http://stackoverflow.com/questions/7429728/changing-image-in-a-wpf-image-from-an-image-sequence-in-the-hard-drive-makes-the - Austin Thompson
@AustinThompson,你帮了我很大的忙,我已经放弃了故事板并改用线程,目前内存似乎很不错,将不得不使用许多照片进行一些测试。非常感谢你的帮助,如果一切顺利,我会授予你赏金。另外,Visual Studio Marketplace Testkit可靠吗?这是应用中心用于认证的测试工具包吗? - M_K
@M_K 很高兴能帮忙。至于Testkit,在我的经验中,自动化测试确实在一些方面发现了问题,避免了我不必要的重新提交。但是手动测试则会因为测试人员的差异而产生变化。肯定值得去做,然后至少浏览一下手动测试。 - Austin Thompson
是的,我已经完成了自动化测试,但是监控测试有点奇怪,说我的应用程序需要83秒才能打开,但实际上它立即打开了。我尝试将赏金添加到你的答案中,但显然之后会自动授予,我不确定它是如何工作的,但你应该会收到它,对吗? - M_K
显示剩余2条评论

1
为了回答你在帖子顶部的编辑,请尝试使用ImageResizer。有一个NuGet包,以及一个HanselBlog上的剧集。显然,这是基于Asp.Net的,但我相信你可以修改它以适应你的情况。

1

通常在设计层面解决这些问题会更有效。

通过一些配置使应用程序智能地了解运行环境,可以使您的应用程序更加健壮。例如,您可以根据可用内存定义一些变量,如图像大小、图像数量、图像质量等,并在应用程序运行时设置这些变量。因此,您的应用程序始终可以在高内存机器上快速运行,在低内存机器上运行缓慢,但永远不会崩溃。(不要认为在托管环境中工作意味着不必担心环境...设计总是很重要的)

此外,还有一些已知的设计模式,例如延迟加载,您可以从中受益。


0

根据您的评论,您正在开发一个延时摄影应用程序。WP7商业延时摄影应用程序将图像压缩为视频而不是静止图像。例如Time Lapse Pro

视频播放的整个目的是将类似或与时间相关的图像压缩成高度压缩的流,这些流不需要大量内存即可播放

如果您可以在应用程序中添加编码为视频的功能,则可以避免尝试模拟视频播放器(使用数百个单个全分辨率帧作为动画书)的问题。

将图像处理为视频服务器端可能是另一种选择(但不如相机友好)。


这个应用程序是否将图像压缩成视频?因为我认为它没有这样做,当你阅读你发布的链接中的评论时,用户会问为什么不能保存为视频并分享到Facebook。我试图找到一种方法来做到这一点,但找不到方法,这就是为什么我暂时不得不这样做的原因。你知道将图像压缩成视频的方法吗? - M_K
他们很可能正在使用自定义库进行压缩,因为在C#中无法进行真正快速的压缩(计算能力不足)。开始寻找用C#编写的视频压缩/播放库,然后您可以使用它。除非您想共享文件,否则您不需要关心编解码器。另一种选择(可以打开许多共享选项)是将文件发送到服务器并在那里使用众多压缩库之一创建视频。您单帧方法的基本问题是您正在移动/存储的数据量太大了。您需要在WP7上任何给定时间都占用更少的内存。 - iCollect.it Ltd

0

好的,一个图像(1024x768)至少有3 mb(argb)的内存大小。

不知道ObjectAnimationUsingKeyFrames是如何内部工作的。也许你可以通过销毁BitmapImage(和KeyFrames)的实例来强制进行垃圾回收,而不会丢失动画中的数据。(不可能,见注释!)


@ck0ne你的计算有误,许多因素决定了.jpg图片的大小。楼主,你可以在保存时(或预处理时)将文件格式更改为.png吗?这通常会得到更小的图片。 - PedroC88
image.finalize() 给我报错了?没有 finalize() 这个扩展方法。 - M_K
作为Java程序员,我会将方法写成小写字母。在WP7中似乎是“image. F inalize()”。如果这是原因的话,我很抱歉。 - ck0ne
我仍然遇到错误,提示无法通过类型为“System.Windows.Media.Imaging.BitmapImage”的限定符访问受保护的成员“object.~Object()”;限定符必须是类型“myApp.ProjectView”(或其派生类) - M_K
我需要保留位图的实例,否则它只会加载最后一张图片。 - M_K
显示剩余9条评论

0

我不太了解Windows Phone,但在.NET WinForms中,执行长时间任务时需要使用单独的线程。您是否正在使用BackgroundWorker或等效工具?终结器线程可能会被阻塞,这将防止图像资源被处理。从UI线程中分离出一个线程将允许Dispose方法自动运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接