Правильный способ рассчитать лучший размер предварительного просмотра камеры, сохраняя пропорции

ВОПРОС

Как программно и точно определить наилучший размер предварительного просмотра для приложения, которое отображает предварительный просмотр камеры с размером экрана устройства? (Или внутри любого вида переменных размеров, действительно).

ПЕРЕД ТЕМ, ЧТОБЫ ВЫ ПЫТАЕТЕСЬ ОТМЕТИТЬ ЭТО КАК ДУБЛИКАТ ОДНОГО ИЗ МИЛЛИОНОВ ДРУГИХ АСПЕКТНЫХ ОТНОШЕНИЙ, КАСАЮЩИХСЯ ОТНОШЕНИЯ НА ТАК, пожалуйста, поймите, что я ищу другое решение, чем то, что обычно доступно. Я задаю этот вопрос, потому что я прочитал так много "ответов", но все они указывают на решение, которое, по моему мнению, является неполным (и потенциально ошибочным, как я опишу здесь). Если это не недостаток, пожалуйста, помогите мне понять, что я делаю неправильно.

Я прочитал много разных реализаций того, как приложения выбирают размеры предварительного просмотра, и большинство из них используют подход, который я называю "достаточно близким" подходом (где вы решаете, какой вариант лучше всего основывать, вычитая соотношение параметров размера из соотношение разрешения экрана и найти вариант с наименьшим значением). Этот подход, кажется, не гарантирует, что он выбирает лучший вариант, но гарантирует, что он не выберет худший вариант.

Например, если я пытаюсь перебрать каждый доступный размер предварительного просмотра на устройстве с разрешением экрана 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 на обоих устройствах, если итерация от минимального до максимального значения. При переборе значений от максимума к минимуму выбирается 176x144 для DeviceA и 176x144 для устройства B. Оба этих варианта (хотя и "достаточно близкие") не являются лучшими.

ВОПРОС

При изучении результатов выше, как я могу программно получить значения, которые "хорошо выглядят"? Я не могу получить эти значения при "достаточно близком" подходе, поэтому я неправильно понимаю взаимосвязь между размером экрана, видом, в котором я отображаю предварительный просмотр, и самими размерами предварительного просмотра. Что мне не хватает?

           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;
}

Очевидно, что этот алгоритм не знает, как выбрать лучший вариант, просто знает, как выбрать вариант, который не является худшим, как любая другая реализация, которую я видел там. Мне нужно понять взаимосвязь между размерами, которые действительно хорошо выглядят, и размерами вида, в которых будет отображаться предварительный просмотр, прежде чем я смогу улучшить свой алгоритм, чтобы фактически выбрать лучший вариант.

Я посмотрел на реализацию того, как с этим справляется проект камеры-cwac CommonWares, и, похоже, он также использует "достаточно близкий" алгоритм. Если я применяю ту же логику к своему проекту, тогда я получаю значения, которые являются достойными, но не "идеального" размера. я получил 1920x1080 назад для обоих устройств. Хотя это значение не является худшим вариантом, оно также слегка сдавлено. Я собираюсь запустить его код в моем тестовом приложении с тестовыми примерами, чтобы определить, будет ли оно слегка сжимать изображение, так как я уже знаю, что оно вернет размер, который не настолько оптимален, как мог бы быть.

2 ответа

Почему лучшие варианты не являются значениями, которые имеют самые низкие значения дельты в соотношениях?

Они есть! Но вам нужно рассчитать соотношение экрана в соответствии с текущей ориентацией экрана!:)

Итак, поскольку ваше устройство находится в ландшафтном режиме, первая таблица должна выглядеть следующим образом:

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) 

Увидеть? Наименьшая абсолютная дельта - ваш лучший выбор.

Вы никогда не получите соотношение, которое "идеально подходит". Поверхность на разных устройствах будет меняться в зависимости от наличия других элементов экрана (строка заголовка, поворот, ...). Вы должны выбрать ближайшее соотношение и расположить предварительный просмотр на экране так, чтобы он либо перетекал по сторонам, либо оставлял (черные) полосы по сторонам (центрируйте его). Посмотрите на это упражнение GitHub, где я пытался решить проблему. Лучшее, что вы можете сделать, это позволить предварительному просмотру "переполнить" поверхность дисплея.

Кроме того, изображение, которое вы получаете после 'takePicture', снова имеет другой размер и соотношение. Есть способ "обрезать" полученное изображение до соотношения предварительного просмотра, если это то, что вы хотите (пример, который я упоминаю, делает это по-другому). Тем не менее проблема, которую вы пытаетесь решить, не может быть полностью решена. Например, если ваша площадь поверхности составляет 1000x800, а ближайший доступный коэффициент предварительного просмотра - 1200x800, вы должны выбрать его и установить здесь значение "fit-in" на false. Теперь вы увидите обрезанную часть выбранного размера предварительного просмотра (100 пикселей переполнены для каждого размера, но кого это волнует, это не видно).

Когда вы получите изображение позже, вы снова должны выбрать тот размер, который соответствует вашим потребностям. Не только самое близкое отношение, но и сам размер изображения. Например, если вы получили размер изображения 2000x1500 (как вы выбрали), и вы хотите, чтобы на экране была видна только часть, вы должны обрезать его до соотношения 1000x800 вашей поверхности (5:4), в результате чего получится изображение 1875x1500. Или, например, если у вас мало памяти и вы используете картинку для загрузки / просмотра, вы можете сделать снимок меньшего размера, например 800x600, и обрезать его до 750x600.

Будьте осторожны, хотя упомянутый мной пример GitHub идет другим путем, он пытается получить окончательное изображение желаемого размера 1600x1200 (соотношение 4:3).

Я надеюсь, что подобрался достаточно близко, чтобы помочь вам с чем-то, на что нельзя ответить простым да / нет. Просто тот факт, что Camera API имеет так много вопросов, здесь указывает на то, что решение не является чистым.

Другие вопросы по тегам