Arrow
Arrow17 апреля 2017 г. 5:36

Оптимизация загрузки изображений

Оптимизация загрузки изображений, QPixmap

Приложение загружает для просмотра изображения с указанной пользователем папки или нескольких папок. Загрузка происходит в отдельном потоке.

Загрузить выходит максимум 20 шт., если больше, то программа на Debian начинает тормозить, а под Windows 7 и вовсе отказывается что-то грузить (грузит 20 штук и дальше ни в какую).

Пробовал использовать частичную загрузку по 15 шт., а дальше по мере прокрутки QScrollArea пользователем догружать остатки - как оказалось не вариант пользователь может перейти к любому изображению (прокрутить скрол). При этом чувствуются тормоза (не успевает все прогрузится) и программа занимает много памяти.

Пробовал также удалить прошлые изображения при до-загрузке новых. В общем возникает масса неудобств и нет возможности навигации по всем изображениям и те же тормоза.

Отсюда назрел вопрос: Есть ли какая-то возможность оптимизировать процесс загрузки или как-то уменьшить вес изображений при их загрузке на отображение?

Пытался сделать так, но не помогло (изображение после компрессии не загружается в pixmap):

        QPixmap pix;

//------------------------------------------------------------------------------

        QByteArray array;
        QBuffer buf(&array, this);
        buf.open(QIODevice::WriteOnly);

        pix.save(&buf, "JPG", 30);
        buf.close();
        array = qCompress(array, 9);

        pix.loadFromData(array, "JPG");   // Здесь pix.size() - 0
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

11
Evgenii Legotckoi
  • 17 апреля 2017 г. 7:03

qCompress однозначно использовать нельзя, поскольку он предназначен просто для компрессии сырых байтов без учёта какого-либо алгоритма организации данных, в данном случае он игнорирует структуру организации данных изображений. Поэтому от такого способа оптимизации сразу отказываемся. Этот инструмент для этого не предназначен.

Что касается остальной части вопроса, то нужно подумать. Вечером может какие мысли появятся, ну и или другие пользователи форума может что подскажут.

    Evgenii Legotckoi
    • 18 апреля 2017 г. 1:19

    А какого вообще размера изображения?

    Вы работаете с изображениями, поэтому программа в любом случае будет потреблять большие объёмы памяти, особенно, если это фотографии в высоком качестве, поэтому здесь только смириться, или уменьшать размер фото, вместе с качеством фото.

    К слову говоря, метод QPixmap::scaledToWidth автоматически уменьшает размер изображения и его качество, думаю, что на потреблении памяти это тоже сказывается, поскольку необходимо держать меньшее количество пикселей в памяти. В принципе вопрос потребления памяти можно несколько нивелировать, если хранить в памяти не оригиналы изображения, а пути к изображению, а в случае изменения размеров каждый погружать изображение из этого пути и масштабировать. Хотя, конечно, тут могут появиться задержки при открытии файла. Что касается подгрузки изображений. Вы замечали, что в файловых менеджерах превью изображений сначала выглядит как обычная иконка, а потом только погружается как положено. Можете применить данный способ отображения. То есть заполнить всё сначала некоторыми иконками, которые подразумевают изображение, а потом уже заполнять картинку, когда пользователь прокрутит. Это несколько сгладит когнитивный диссонанс у пользователя.

    Тем более, если вернуться к просмоторщикам, тот же самый встроенный просмоторщик pdf в Linux тоже не отличается идеальной скоростью при открытии очень больших pdf. Так что некоторые задержки будут всегда, особенно на слабом железе.

    Но если у вас программа падает или виснет всего на 20 изображениях, то это говорит о проблемах в логике кода. То есть это скорее не вопрос оптимизации, как я полагаю. Например, в одном из наших обсуждений по этой тематике, я же набросал пробную программку со скроллингом изображений. Она вполне неплохо открывает 200-300 фотографий и не виснет. Хотя я понимаю, что у вас логика уже явно сложнее, но это лишь говорит о том, что где-то, что-то вы делаете не так. Да, в моём варианте видно, что фотографии грузятся, а не всё сразу отображается, но для этого и служат всякие прогресс бары, чтобы показываться процесс загрузки. Поэтому подумайте, над тем, что я написал. Задача скорее не в том, чтобы оптимизировать, а в том, чтобы найти ошибку, поскольку 20 изображений - это не такая большая нагрузка, чтобы повешать приложение на современно ПК, а открываться эти 20 изображении будут секунды, не более.

      Evgenii Legotckoi
      • 18 апреля 2017 г. 1:24
      • Ответ был помечен как решение.

      Вот, прикладываю мою пробную программку, просто посмотрите как она работает на большом фото архиве. Где фотографий 300 будет. Там кнопка на открытие диалога, в диалоге переходите в каталог с фотками и выделяете все фотографии.

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

        Arrow
        • 18 апреля 2017 г. 8:11

        Спасибо!

        У меня грузит с такой же скоростью.

        Изображение: 2400х3400 (23,5 МБт).

        У меня происходит дополнительная обработка изображений и она как оказалось и дает нагрузку.

        Похоже от этого не уйти никак, придется смириться.

        Если показывать подставную картинку выходит очень даже ничего. :)

        Пара вопросов по вашему коду:

        1. Из-за любопытства (чисто для себя)- Почему вы некоторые заголовочные файлы подключаете в файле реализации (*.cpp), а не в заголовочном (*. h) и где правильнее?

        2. По незнанию всех функций Qt:

        // Почему так
        
        QString str = QFileDialog::getOpenFileName(this,
                                    "Выберите папку с изображениями",
                        QStandardPaths::locate(QStandardPaths::HomeLocation, QString()),
                                    "Изображения (*.jpg | *.bmp | *. png | *.tif)");
        
        // А не так
        
        QString str = QFileDialog::getOpenFileName(this,
                                    "Выберите папку с изображениями",
                        QDir::homePath(),
                                    "Изображения (*.jpg | *.bmp | *. png | *.tif)");
          Evgenii Legotckoi
          • 18 апреля 2017 г. 8:42

          Если изображение 23 Мб весит, то тут да... тяжко будет открывать любому редактору.

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

          Могу только посоветовать ещё порыть в направлении распараллеливания подгрузки изображений по ядрам процессора, то есть не просто в одном отдельном потоке всё делать, а делать в 4-х потоках, если процессор 4-х ядерный. Посмотрите в сторону QThreadPool . К сожалению, у меня статей на эту тему нет, но подумаю, может на теме изображений и скроллинга накидаю статью с QThreadPool

          Что касается прочих вопросов.

          1. Заголовочные файлы иных классов следует подключать в cpp файлах, только если эти классы не объявляются в заголовочном классе разрабатываемого файла. То есть Если QUrl не используется в объявлении класса или нет методов, которые его принимают или возвращают, но он используется в коде реализации, то заголовочный файл QUrl следует поместить в файл cpp. Это несколько увеличивает скорость компиляции, поскольку компилятору не требуется постоянно перепроверять, было ли что-то подключено в заголовочных файлах или нет. Нагрузка несколько снижается.

          2. QStandardPaths даёт более расширенный функционал по определению путей к ключевым директориям. Когда разбирался с этим под Андроид, то постоянно пользовался именно QStandardPaths . Почитайте документацию на этот класс, там много разных путей, а QDir ограничен только домашней директорией. Впрочем для использования домашней директории как раз хватает QDir::homePath() , но я просто изначально пользовался QStandardPaths , и даже внимания не обратил на данный метод при чтении документации, так что это из разряда "так получилось ". Так что спасибо, что обратили внимания на данный момент.

            Arrow
            • 18 апреля 2017 г. 9:13

            Я тут подумал, а что если сделать загрузку изображения не в QLabel, а через прорисовку квадрата QRect и на нем при помощи QPainter рисовать изображение.

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

            Или это гиблое дело и ничего выиграть не выйдет?

              Evgenii Legotckoi
              • 18 апреля 2017 г. 9:28

              Вообще QRect - это просто геометрическая область, которая не имеет никакого понятия о рисовании. У него даже метода paintEvent() нет. Поэтому я не особо понимаю, что вы имели ввиду под этим. А так отрисовка что в QLabel , что в QWidget будет технически одинаково затратной, поскольку QLabel наследован от QWidget , да и техническая реализация будет ненамного больше отличаться, разве только у QWidget будете переопределять метод paintEvent() Да там реализовывать отрисовку изображения. Кода получится больше - это факт.

              Возможно, получится ускориться, если использовать QOpenGLWidget вместо QLabel. Тогда будет графическая подсистема задействоваться. Но это даст эффект только для отрисовки. А у вас ещё дополнительная обработка, которая ложится на CPU.

              Поэтому основные затраты у вас - это дополнительная обработка, вот она и влияет. А уменьшение количества пикселей и так скорее всего происходит во время выполнения метода scaledToWidth . Качество уж точно падает.

              Так что, наиболее эффективным сейчас будет - это внедрение многопоточности, то есть использование всех возможных процессоров на ПК. Именно дополнительную обработку нужно вынести в отдельные потоки.

                Arrow
                • 18 апреля 2017 г. 9:54

                Спасибо! Попытаюсь реализовать для начала два потока, посмотрю что даст.

                  Arrow
                  • 19 апреля 2017 г. 8:34

                  Вы пишите, что можно распараллелить подгрузку изображений по ядрам процессора при помощи QThreadPool. Таипа такого:

                  class ImageLoader : public QObject, public QRunnable
                  
                  //-----------------------------------
                  
                  ImageLoader *imgLoader = new ImageLoader();
                  
                      // Обработка изображений
                      connect(imgLoader, &ImageLoader::sendPixmap, this, &MainWindow::addImage);
                  
                      QThreadPool::globalInstance()->start(imgLoader);

                  Чем этот метод отличается от QThread:

                  class ImageLoader : public QObject
                  
                  //-------------------------------------------------------
                  
                     QThread* thread = new QThread;
                      ImageLoader *imgLoader = new ImageLoader();
                  
                      connect(thread, &QThread::started, imgLoader, &ImageLoader::process);
                  
                      connect(imgLoader, &ImageLoader::finished, thread, &QThread::terminate);
                  
                      connect(thread, &QThread::finished, imgLoader, &ImageLoader::deleteLater);
                  
                      connect(imgLoader, &ImageLoader::sendPixmap, this, &MainWindow::addImage);
                  
                      imgLoader->moveToThread(thread);
                      thread->start();

                  И что в данном случае более правильно использовать?

                    Evgenii Legotckoi
                    • 19 апреля 2017 г. 12:47

                    Метод с QThreadPool отличается тем, что по умолчанию работает с количеством потоков равным количеству процессоров на ПК. Метода QThreadPool::idealThreadCount() позволяет узнать, сколько процессоров имеется в системе. В случае же с QThread, нужно будет следить за количеством процессоров и создавать необходимое количество этих потоков а потом удалять их. QThreadPool же делает это в автоматическом варианте.

                    Фактически нужно будет в зависимости от количества ядер процессора создать несколько объектов ImgLoader и поместить их в QThreadPool . Важным моментом для Вас будет то, что необходимо будет разделить весь список путей к файлам на количество процессоров. То есть, если берём 120 файлов, а количество процессоров(ядер) равно 4, то нужно будет создать 4 ImgLoader, каждый из которых будет обрабатывать по 30 файлов из этих 120. Ну и добавить немного логики, чтобы эти ImgLoader не передрались друг с другом, а также все изображения загружались в нужной последовательности.

                    В вашей задаче будет правильнее использовать QThreadPool, поскольку здесь не просто убираем в отдельные потоки всю логику по загрузке картинок, а ещё её нужно и распределить по потокам, чтобы ускорить процесс. Ну а вариант с одним QThread можно использовать для задач попроще, например, когда нужно проверять какое-то одно событие, которое имеет смысл обрабатывать в отдельном потоке. Зачастую такое необходимо, чтобы увязать с программой на Qt некие внешние SDK, которые ещё могут быть написаны и на Си.

                      Arrow
                      • 19 апреля 2017 г. 15:09

                      Понял, спасибо!

                        Комментарии

                        Только авторизованные пользователи могут публиковать комментарии.
                        Пожалуйста, авторизуйтесь или зарегистрируйтесь
                        B

                        C++ - Тест 002. Константы

                        • Результат:16баллов,
                        • Очки рейтинга-10
                        B

                        C++ - Тест 001. Первая программа и типы данных

                        • Результат:46баллов,
                        • Очки рейтинга-6
                        FL

                        C++ - Тест 006. Перечисления

                        • Результат:80баллов,
                        • Очки рейтинга4
                        Последние комментарии
                        k
                        kmssr8 февраля 2024 г. 18:43
                        Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                        АК
                        Анатолий Кононенко5 февраля 2024 г. 1:50
                        Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                        EVA
                        EVA25 декабря 2023 г. 10:30
                        Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                        J
                        JonnyJo25 декабря 2023 г. 8:38
                        Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                        G
                        Gvozdik18 декабря 2023 г. 21:01
                        Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                        Сейчас обсуждают на форуме
                        P
                        Pisych27 февраля 2023 г. 4:04
                        Как получить в массив значения из связанной модели? Спасибо, разобрался:))
                        AC
                        Alexandru Codreanu19 января 2024 г. 11:57
                        QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
                        BlinCT
                        BlinCT27 декабря 2023 г. 8:57
                        Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
                        Дмитрий
                        Дмитрий10 января 2024 г. 4:18
                        Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
                        Evgenii Legotckoi
                        Evgenii Legotckoi12 декабря 2023 г. 6:48
                        Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

                        Следите за нами в социальных сетях