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

时间:2015-03-04 17:35:54

标签: java android android-camera aspect-ratio cwac-camera

问题

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

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

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

例如,如果我尝试在屏幕分辨率为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)

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

问题

在研究上面的结果时,我如何以编程方式推导出看起来很好的价值?#34;?我不能用"足够接近"来获得这些价值。接近所以我误解了屏幕大小之间的关系,我正在显示预览的视图和预览大小本身。我错过了什么?

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

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

基于上述观察结果(最佳选项不是比率增量最小的值)我开发了这种算法,它迭代所有可能的预览尺寸,检查它是否符合我的"足够接近& #34;条件,存储符合此条件的大小,最后找到至少大于或等于提供宽度的值。

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项目如何处理这个问题的实现,它似乎也使用了&#34;足够接近&#34;算法。如果我将相同的逻辑应用于我的项目,那么我会找回体面但不完美的价值观。尺寸。我得到1920x1080两台设备。虽然这个价值不是最糟糕的选择,但它也略微受到挤压。我将在我的测试应用程序中运行他的代码和测试用例,以确定它是否也稍微调整了图像,因为我已经知道它将返回一个不是最佳的大小。

2 个答案:

答案 0 :(得分:3)

  

为什么最佳选项不是比率增量最低的值?

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

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

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) 

请参阅?最小的绝对 delta是最佳选择

答案 1 :(得分:0)

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

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

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

但要小心,我提到的GitHub示例是另一种方式,它试图获得所需大小1600x1200(比例4:3)的最终图片。

我希望我足够接近帮助你做一些简单的肯定/不能回答的事情。事实上,Camera API在这里有很多问题,这表明解决方案并不干净。