计算最佳相机预览尺寸的正确方法保持纵横比

Posted

技术标签:

【中文标题】计算最佳相机预览尺寸的正确方法保持纵横比【英文标题】:Correct way to calculate best camera preview size maintaing aspect ratio 【发布时间】:2015-05-05 20:00:28 【问题描述】:

问题

您如何以编程方式准确确定以设备屏幕尺寸显示相机预览的应用程序的最佳预览尺寸? (或者在任何可变维度的视图中,真的)。

在您尝试将此标记为与其他数百万个宽高比相关问题之一的重复之前,请理解我正在寻找与通常可用的解决方案不同的解决方案。我问这个问题是因为我已经阅读了很多“答案”,但它们都指向我认为不完整的解决方案(并且可能存在缺陷,正如我将在此处描述的那样)。如果这不是缺陷,请帮助我理解我做错了什么。

我已经阅读了很多关于应用程序如何选择预览大小的不同实现,其中大多数采用我称之为“足够接近”的方法(您可以根据减去大小的比率来决定哪个选项最好从屏幕分辨率的比例中选择选项并找到具有最低值的选项)。这种方法似乎不能确保它选择最佳选项,但确保它不会选择最差选项

例如,如果我尝试在屏幕分辨率为 720x1184 的设备上迭代每个可用的预览大小并全屏显示预览 (720x1184),这里是相机预览的结果(按以下选项排序)最接近屏幕分辨率的格式为abs(ratio of size option - screen resolution ratio))。所有尺寸均来自.getSupportedPreviewSizes)。 (“结果”来自使用测试用例的视觉观察,该测试用例使用出现在相机取景器中的静态圆圈,不是程序化的)

720.0/1184.0 = 0.6081081081081081

Res         Ratio         Delta in ratios  Result       
----------------------------------------------------   
176/144   = 1.22222222222 (0.614114114114) (vertically stretched)
352/288   = 1.22222222222 (0.614114114114) (vertically stretched)
320/240   = 1.33333333333 (0.725225225225) (vertically stretched)
640/480   = 1.33333333333 (0.725225225225) (vertically stretched)
720/480   = 1.5           (0.891891891892) (vertically stretched)
800/480   = 1.66666666667 (1.05855855856)  (looks good)
640/360   = 1.77777777778 (1.16966966967)  (horizontally squashed)
1280/720  = 1.77777777778 (1.16966966967)  (slight horizontal squash)
1920/1080 = 1.77777777778 (1.16966966967)  (slight horizontal squash) 

如果不在另一台设备上运行它,它就不是一个正确的 android 代码测试。以下是在屏幕分辨率为 800x1216 的设备上显示相机预览并以相同分辨率显示预览的结果 (800x1216)

800/1216.0 = 0.657894736842

Res         Ratio         Delta in ratios  Results
------------------------------------------------
176/144   = 1.22222222222 (0.56432748538)  (looks vertically stretched)
352/288   = 1.22222222222 (0.56432748538)  (looks vertically stretched)
480/368   = 1.30434782609 (0.646453089245) (looks vertically stretched)
320/240   = 1.33333333333 (0.675438596491) (looks vertically stretched)
640/480   = 1.33333333333 (0.675438596491) (looks vertically stretched)
800/600   = 1.33333333333 (0.675438596491) (looks vertically stretched)
480/320   = 1.5           (0.842105263158) (looks good)
720/480   = 1.5           (0.842105263158) (looks good)
800/480   = 1.66666666667 (1.00877192982)  (looks horizontally squashed)
960/540   = 1.77777777778 (1.11988304094)  (looks horizontally squashed)
1280/720  = 1.77777777778 (1.11988304094)  (looks horizontally squashed)
1920/1080 = 1.77777777778 (1.11988304094)  (looks horizontally squashed)
864/480   = 1.8           (1.14210526316)  (looks horizontally squashed)

“足够接近”的方法(假设比率中的任何增量等于或小于1.4d 是可以接受的)如果迭代从最低值到最高值,将在两个设备上返回1920x1080。如果遍历从最高值到最低值,它将为 DeviceA 选择 176x144,为 DeviceB 选择 176x144。这两个选项(尽管“足够接近”)都不是最佳选项。

问题

在研究上述结果时,我如何以编程方式得出“看起来不错”的值?我无法使用“足够接近”的方法获得这些值,所以我误解了屏幕尺寸、我正在显示预览的视图和预览尺寸本身之间的关系。我错过了什么?

           Screen dimensions      = 720x1184
             View dimensions      = 720x1184
Screen and View Aspect Ratio      = 0.6081081081081081
Best Preview Size ratio (720x480) = 1.5  

为什么最好的选择不是比率变化量最小的值? 结果令人惊讶,因为其他人似乎都认为最好的选择是通过计算比率的最小差异,但我看到的是最好的选择似乎在所有选项的中间。并且它们的宽度更接近将显示预览的视图的宽度。

基于上述观察(最好的选择不是比率增量最小的值)我开发了这个算法,它迭代所有可能的预览大小,检查它是否符合我的“足够接近”标准,存储满足此条件的尺寸,并最终找到至少大于或等于提供的宽度的值。

public static Size getBestAspectPreviewSize(int displayOrientation,
                                            int width,
                                            int height,
                                            Camera.Parameters parameters,
                                            double closeEnough) 


    double targetRatio=(double)width / height;
    Size bestSize = null;

    if (displayOrientation == 90 || displayOrientation == 270) 
        targetRatio=(double)height / width;
    

    List<Size> sizes=parameters.getSupportedPreviewSizes();
    TreeMap<Double, List> diffs = new TreeMap<Double, List>();


    for (Size size : sizes) 

        double ratio=(double)size.width / size.height;

        double diff = Math.abs(ratio - targetRatio);
        if (diff < closeEnough)
            if (diffs.keySet().contains(diff))
                //add the value to the list
                diffs.get(diff).add(size);
             else 
                List newList = new ArrayList<Camera.Size>();
                newList.add(size);
                diffs.put(diff, newList);
            

            Logging.format("usable: %sx%s %s", size.width, size.height, diff);
        
    

    //diffs now contains all of the usable sizes
    //now let's see which one has the least amount of
    for (Map.Entry entry: diffs.entrySet())
        List<Size> entries = (List)entry.getValue();
        for (Camera.Size s: entries) 

            if (s.width >= width && s.height >= width) 
                bestSize = s;
            
            Logging.format("results: %s %sx%s", entry.getKey(), s.width, s.height);
        
    

    //if we don't have bestSize then just use whatever the default was to begin with
    if (bestSize==null)
        if (parameters.getPreviewSize()!=null)
            bestSize = parameters.getPreviewSize();
            return bestSize;
        

        //pick the smallest difference in ratio?  or pick the largest resolution?
        //right now we are just picking the lowest ratio difference
        for (Map.Entry entry: diffs.entrySet())
            List<Size> entries = (List)entry.getValue();
            for (Camera.Size s: entries) 
                if (bestSize == null)
                    bestSize = s;
                
            
        
    

    return bestSize;

很明显,这个算法不知道如何选择最好的选项,只知道如何选择一个不像我见过的所有其他实现那样最差的选项。在改进算法以实际选择最佳选项之前,我需要了解实际看起来不错的尺寸与将显示预览的视图尺寸之间的关系。

我查看了CommonWares' camera-cwac 项目如何处理这个问题的实现,它似乎也使用了“足够接近”的算法。如果我将相同的逻辑应用于我的项目,那么我会得到不错的值,但不是“完美”的大小。两种设备我都收到了1920x1080。尽管该值不是最糟糕的选择,但它也被略微挤压了。我将在带有测试用例的测试应用程序中运行他的代码,以确定它是否也会稍微挤压图像,因为我已经知道它会返回一个不是最佳的尺寸。

【问题讨论】:

我认为您需要重新计算显示比例,然后再计算增量,因为显示器处于纵向模式。 【参考方案1】:

为什么最好的选项不是比率变化量最小的值?

他们是!但是你需要根据你当前的屏幕方向来计算你的屏幕比例! :)

因此,由于您的设备处于横向模式,第一个表格应如下所示:

1184.0/720.0 = 1.6444444

Res         Ratio         Delta in ratios  Result       
----------------------------------------------------   
176/144   = 1.222 ( 0.422) (vertically stretched)
352/288   = 1.222 ( 0.422) (vertically stretched)
320/240   = 1.333 ( 0.311) (vertically stretched)
640/480   = 1.333 ( 0.311) (vertically stretched)
720/480   = 1.500 ( 0.144) (vertically stretched)
800/480   = 1.666 (-0.016) (looks good)
640/360   = 1.777 (-0.126) (horizontally squashed)
1280/720  = 1.777 (-0.126) (slight horizontal squash)
1920/1080 = 1.777 (-0.126) (slight horizontal squash) 

看到了吗?最小的绝对增量是您的最佳选择

【讨论】:

看看那个! :D 那么横向模式是所有设备的默认模式,还是我可以查询设备? 我不知道。我非常有信心它不是所有设备的默认设置。您绝对可以查询设备的方向。 好吧,你应该获取增量值。你关心的是差异是否更小。 getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getOrientation(); 这是在运行时获取屏幕方向的方法。 您可以查询方向,但这只会为您提供当前方向(如果用户以纵向或横向模式握住它),而不是应该确定我是否应该根据以下值计算比率的值w/h 或 h/w。【参考方案2】:

你永远不会得到“完美”的比例。不同设备上的表面会因其他屏幕项目(标题栏、旋转...)的存在而改变。您必须选择最接近的比例并将预览定位在屏幕上,以便它在侧面溢出或在侧面留下(黑色)条带(居中)。看看我试图解决这个问题的GitHub exercise。您能做的最好的事情就是让预览“溢出”显示表面。

此外,您在“takePicture”之后获得的图片再次具有不同的大小和比例。如果这是您想要的,有一种方法可以将生成的图片“裁剪”为预览的比例(我提到的示例不同)。尽管如此,您试图解决的问题仍然无法完美解决。例如,如果您的表面积为 1000x800,而您最接近的可用预览比例为 1200x800,则您必须选择该比例并将 'fit-in' here 设置为 false。现在,您将看到所选预览尺寸的裁剪部分(每个尺寸都有 100 像素溢出,但谁在乎,它是不可见的)。

当您稍后获得图片时,您必须再次选择适合您需要的尺寸。不仅是最接近的比例,还有图片大小本身。例如,如果您的图片尺寸为 2000x1500(根据您的选择)并且您只想保留屏幕上可见的部分,则必须将其裁剪为表面的 1000x800 比例(5:4),从而生成图片 1875x1500。或者,例如,如果您的内存不足并将图片用于上传/查看,您可以拍摄较小的图片尺寸(如 800x600)并将其裁剪为 750x600。

不过要小心,我提到的 GitHub 示例是相反的,它试图获得所需尺寸 1600x1200(比例 4:3)的最终图片。

我希望我能帮助您解决一些无法用简单的是/否回答的问题。只是Camera API在这里有这么多问题表明解决方案并不干净。

【讨论】:

溢出视图边界似乎是一个不错的方法,因为图像不会失真——唯一棘手的部分是裁剪最终图像。在我的场景中,我有一个正方形FrameLayout,它充当“面具”,只会让用户看到相机SurfaceView 的正方形部分(想想Instagram 相机视图)。它总是一个正方形,所以最终图像的尺寸为screenWidth x screenWidth。假设我的预览尺寸和图片尺寸不同,您能否指出我如何裁剪图像的正确方向? 所以我将始终以设备的全分辨率显示我的预览,但用户只会看到其中的正方形部分。 @MaxWorg 假设您正在运行 Android Studio,尝试拉取 GitHub 示例(我相信 FIT_IN 设置为 'true'),选择 1:1 的预览(希望相机有一个)和玩这个代码部分:github.com/seanpjanson/AndyCam/blob/master/src/main/java/com/… 如果没有找到比率 1.000,请选择最接近的一个并使用方形模板(FrameLayout 包含 SurfaceView + Imageview 和方形透明开口)遮盖表面。抱歉,我忙于处理完全不相关的 Android 问题。

以上是关于计算最佳相机预览尺寸的正确方法保持纵横比的主要内容,如果未能解决你的问题,请参考以下文章

即使设置了正确的尺寸,相机预览也会被拉伸

确保相机预览大小/纵横比与生成的视频相匹配

4:3 平板电脑上的 Android 相机预览纵横比

如何设置 3:4 纵横比 Flutter 相机预览

如何在Android中修复相机预览(surfaceview)的正确纵横比?

Android Camera 2 预览尺寸和设备纵横比