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

Max*_*org 7 java android aspect-ratio android-camera 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) 
Run Code Online (Sandbox Code Playgroud)

如果没有在另一台设备上运行它,它将不是一个正确的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)
Run Code Online (Sandbox Code Playgroud)

如果迭代通过最低值到最高值,则"足够接近"的方法(假设任何比率的delta等于或小于1.4d可接受的值)将1920x1080在两个设备上返回.如果迭代最高值到最低值,它将选择176x144DeviceA和176x144DeviceB.这两个选项(尽管"足够接近")并不是最好的选择.

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

           Screen dimensions      = 720x1184
             View dimensions      = 720x1184
Screen and View Aspect Ratio      = 0.6081081081081081
Best Preview Size ratio (720x480) = 1.5  
Run Code Online (Sandbox Code Playgroud)

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

显然,这个算法不知道如何选择最佳选项只知道如何选择一个不是最差的选项,就像我在那里看到的其他实现一样.我需要了解实际看起来很好的尺寸与预览将显示的视图尺寸之间的关系,然后才能改进算法以实际选择最佳选项.

我已经看过CommonWares的camera-cwac项目如何处理这个问题的实现,它似乎也使用了"足够接近"的算法.如果我将相同的逻辑应用于我的项目,那么我会找回体面但不是"完美"大小的值.我1920x1080回来了两个设备.虽然这个值不是最糟糕的选择,但它也略微被压扁了.我将在我的测试应用程序中运行他的代码和测试用例,以确定它是否也稍微调整了图像,因为我已经知道它将返回一个不尽可能最佳的大小.

sea*_*npj 1

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

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

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

但要小心,我提到的 GitHub 示例却采用了相反的方式,它尝试获得所需尺寸 1600x1200(比例 4:3)的最终图片。

我希望我能足够接近地帮助您解决一些无法用简单的是/否来回答的问题。事实上,Camera API 这里有这么多问题,这表明解决方案并不干净。