Реализация приложения

main
Евгений Тетерин 2025-07-19 23:10:05 +03:00
parent cafb449565
commit bf5ba2e664
39 changed files with 2786 additions and 1 deletions

107
.gitignore vendored 100644
View File

@ -0,0 +1,107 @@
.build/
_other/
_distrib*/
# Tmp files
*~
Thumbs.db*
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
*.ko
*.obj
*.elf
*.lib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
*_qmlcache.qrc
# ---> C
# Prerequisites
*.d
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# Fortran module files
*.mod
*.smod

8
CMakeLists.txt 100644
View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 4.0)
project (OpenCvExample)
# application:
add_subdirectory(sources)

9
LICENSE 100644
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

25
LICENSE.ru 100644
View File

@ -0,0 +1,25 @@
MIT лицензия
Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
Данная лицензия разрешает лицам, получившим копию
данного программного обеспечения и сопутствующей документации
(в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно
использовать Программное Обеспечение без ограничений,
включая неограниченное право на использование, копирование, изменение,
слияние, публикацию, распространение, сублицензирование и/или продажу
копий Программного Обеспечения, а также лицам, которым предоставляется
данное Программное Обеспечение, при соблюдении следующих условий:
Указанное выше уведомление об авторском праве и данные условия
должны быть включены во все копии или значимые части данного Программного Обеспечения.
ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ»,
БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ,
ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ
НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ.
НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ
ПО КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ,
ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ,
ВОЗНИКШИМ ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.

View File

@ -1,3 +1,14 @@
# OpenCV_Example
Пример работы с библиотекой обработки изображений OpenCV
Пример работы с библиотекой обработки изображений OpenCV
В проекте используются компилятор и библиотеки из MSYS2 для Mingw64
## Внешний вид готового приложения (Qt6, OC Windows 11)
![picture](/_resources/images/screenshot.png)

View File

@ -0,0 +1,28 @@
# Файл для подключения в основной проект через include
# Настройки для приложений
include(${CMAKE_CURRENT_LIST_DIR}/common.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/developer.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
# Имя выходного файла совпадает с названием проекта:
set(RUNTIME_OUTPUT_NAME ${PROJECT_NAME})
configure_file(
${CMAKE_CURRENT_LIST_DIR}/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
@ONLY
)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${DISTRIB_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${DISTRIB_DIR})
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
configure_file(
${CMAKE_CURRENT_LIST_DIR}/versioninfo.rc.in
${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc
@ONLY
)
endif()

View File

@ -0,0 +1,54 @@
# Общие настройки для всех типов проектов
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
if (CMAKE_COMPILER_IS_GNUCXX)
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0.0")
set(CMAKE_CXX_STANDARD 20)
else()
set(CMAKE_CXX_STANDARD 23)
endif()
else()
set(CMAKE_CXX_STANDARD 23)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Поиск библиотек Qt:
find_package(QT NAMES Qt6 Qt5 REQUIRED)
# Настройки каталогов:
include(${CMAKE_CURRENT_LIST_DIR}/setup_directories.cmake)
# Определение разрядности:
if (${CMAKE_SIZEOF_VOID_P} STREQUAL 4)
set(DIR_PREFIX "32")
elseif (${CMAKE_SIZEOF_VOID_P} STREQUAL 8)
set(DIR_PREFIX "64")
endif ()
# Каталог для готовых приложений и библиотек после компиляции:
set(DISTRIB_DIR
${ROOT_PROJECT_DIR}_distrib/${CMAKE_SYSTEM_NAME}_Qt${QT_VERSION}_${DIR_PREFIX}-bit
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(IS_DEBUG TRUE)
set(IS_RELEASE FALSE)
else()
set(IS_DEBUG FALSE)
set(IS_RELEASE TRUE)
endif()
message(STATUS "Project '${PROJECT_NAME}' compiler ${CMAKE_CXX_COMPILER} version: ${CMAKE_CXX_COMPILER_VERSION}")
message(STATUS "Project '${PROJECT_NAME}' compiler dir: '${CXX_COMPILER_DIR}'")
message(STATUS "Project '${PROJECT_NAME}' cmake dir: '${CMAKE_BIN_DIR}'")
message(STATUS "Project '${PROJECT_NAME}' distrib dir: '${DISTRIB_DIR}'")
message(STATUS "Project '${PROJECT_NAME}' IS_RELEASE: ${IS_RELEASE}, IS_DEBUG: ${IS_DEBUG}")
message(STATUS "Project '${PROJECT_NAME}' CMAKE_PREFIX_PATH: '${CMAKE_PREFIX_PATH}'")
message(STATUS "Project '${PROJECT_NAME}' CMAKE_SYSTEM_LIBRARY_PATH: '${CMAKE_SYSTEM_LIBRARY_PATH}'")

View File

@ -0,0 +1,9 @@
#define PROG_NAME "@PROJECT_NAME@"
#define PROG_VERSION "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUM@@PROJECT_VERSION_TWEAK@"
#define PROG_CAPTION "@PROJECT_NAME@ v"
#define PROG_DESCRIPTION "@PROJECT_DESCRIPTION@"
#define SOFT_DEVELOPER "@SOFT_DEVELOPER@"
#define DEVELOPER_DOMAIN "@DEVELOPER_DOMAIN@"
#define BUILD_DATE "@BUILD_DATE@"
#define BUILD_NUM "@BUILD_NUM@"
#define ORIGINAL_FILE_NAME "@ORIGINAL_FILE_NAME@"

View File

@ -0,0 +1,289 @@
# Определение зависимостей после сборки приложения
cmake_minimum_required(VERSION 4.0)
set(LIBS_FIND_DIRS "${PREFIX}")
set(QT_SHARE_DIR "${PREFIX}")
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
if (EXISTS "${PREFIX}/bin")
set(LIBS_FIND_DIRS "${PREFIX}/bin")
endif()
else()
if (EXISTS "${PREFIX}/lib")
set(LIBS_FIND_DIRS "${PREFIX}/lib")
endif()
endif()
if (EXISTS "${PREFIX}/share/qt${QT_VERSION_MAJOR}")
set(QT_SHARE_DIR "${PREFIX}/share/qt${QT_VERSION_MAJOR}")
endif()
if(TARGET_TYPE STREQUAL "EXECUTABLE")
if (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}")
set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}")
elseif (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}.exe")
set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}.exe")
endif()
else()
if (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}")
set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}")
elseif (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}.dll")
set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}.dll")
elseif (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}.so")
set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}.so")
endif()
endif()
message(STATUS "TARGET_FILE: '${TARGET_FILE}'")
message(STATUS "LIBS_FIND_DIRS: '${LIBS_FIND_DIRS}'")
message(STATUS "OUTPUT_DIR: '${OUTPUT_DIR}'")
message(STATUS "QT_VERSION_MAJOR: '${QT_VERSION_MAJOR}'")
message(STATUS "Find and copy dependencies. Please, wait...")
# functions --------------------------------------------------------------------
# Копирование файла
function(safe_copy src dest)
# Пытаемся скопировать через execute_process (кросс-платформенно)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${src}" "${dest}"
RESULT_VARIABLE copy_result
ERROR_VARIABLE copy_error
OUTPUT_QUIET # Подавляем stdout
)
if(NOT copy_result EQUAL 0)
message(WARNING "Не удалось скопировать ${src} -> ${dest}: ${copy_error}")
return()
endif()
endfunction()
# Отслеживание цепочки ссылок
function(get_symlink_chain dep symlink_chain)
unset(chain)
set(current_dep "${dep}")
# 1. Разрешаем основную цепочку симлинков (исходный файл конечный файл)
while(IS_SYMLINK "${current_dep}")
execute_process(
COMMAND readlink "${current_dep}"
OUTPUT_VARIABLE link_target
OUTPUT_STRIP_TRAILING_WHITESPACE
)
list(INSERT chain 0 "${current_dep}") # Добавляем текущий симлинк в начало
get_filename_component(parent_dir "${current_dep}" DIRECTORY)
set(current_dep "${parent_dir}/${link_target}") # Переходим по ссылке
endwhile()
# 2. Добавляем конечный файл в начало цепочки
list(INSERT chain 0 "${current_dep}")
# 3. Теперь ищем ВСЕ симлинки в этой директории, которые ссылаются на конечный файл
if(EXISTS "${current_dep}")
get_filename_component(final_file "${current_dep}" REALPATH) # Абсолютный путь конечного файла
get_filename_component(parent_dir "${current_dep}" DIRECTORY) # Директория конечного файла
# Получаем список всех файлов в этой директории
file(GLOB all_files LIST_DIRECTORIES false "${parent_dir}/*")
foreach(file IN LISTS all_files)
# Если это симлинк, проверяем, ведёт ли он на конечный файл
if(IS_SYMLINK "${file}")
execute_process(
COMMAND readlink -f "${file}" # Абсолютный путь цели симлинка
OUTPUT_VARIABLE symlink_target
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Если цель симлинка совпадает с конечным файлом, добавляем в цепочку
if(symlink_target STREQUAL final_file AND NOT file IN_LIST chain)
list(APPEND chain "${file}")
endif()
endif()
endforeach()
endif()
set(${symlink_chain} "${chain}" PARENT_SCOPE)
endfunction()
# ------------------------------------------------------------------------------
if (EXISTS "${TARGET_FILE}")
# Получаем runtime-зависимости
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES ${TARGET_FILE}
RESOLVED_DEPENDENCIES_VAR RESOLVED_DEPS
UNRESOLVED_DEPENDENCIES_VAR UNRESOLVED_DEPS
DIRECTORIES ${LIBS_FIND_DIRS}
)
set(USE_SQL OFF)
set(USE_PRINT OFF)
set(USE_NETWORK OFF)
set(USE_SERIALPORT OFF)
set(USE_MULTIMEDIA OFF)
message(STATUS "\nResolved dependencies copy:\n")
foreach(DEP ${RESOLVED_DEPS})
string(FIND "${DEP}" "${LIBS_FIND_DIRS}" POS)
if(POS EQUAL 0)
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
message(STATUS "${DEP}")
file(COPY "${DEP}" DESTINATION "${OUTPUT_DIR}")
else ()
#message(STATUS "${DEP}")
#file(COPY "${DEP}" DESTINATION "${OUTPUT_DIR}/system_lib" FOLLOW_SYMLINK_CHAIN)
get_symlink_chain("${DEP}" dep_chain)
foreach(file IN LISTS dep_chain)
message(STATUS "${file}")
safe_copy("${file}" "${OUTPUT_DIR}")
endforeach()
endif()
endif()
string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Sql" POS)
if(POS GREATER 0)
set(USE_SQL ON)
endif()
string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Print" POS)
if(POS GREATER 0)
set(USE_PRINT ON)
endif()
string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Network" POS)
if(POS GREATER 0)
set(USE_NETWORK ON)
endif()
string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Serial" POS)
if(POS GREATER 0)
set(USE_SERIALPORT ON)
endif()
string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Multimedia" POS)
if(POS GREATER 0)
set(USE_MULTIMEDIA ON)
endif()
endforeach()
if (EXISTS "${LIBS_FIND_DIRS}/libQt${QT_VERSION_MAJOR}XcbQpa.so")
get_symlink_chain("${LIBS_FIND_DIRS}/libQt${QT_VERSION_MAJOR}XcbQpa.so" xcb_chain)
foreach(file IN LISTS xcb_chain)
message(STATUS "${file}")
safe_copy("${file}" "${OUTPUT_DIR}")
endforeach()
endif()
message(STATUS "\nResolved dependencies not copy:\n")
foreach(DEP ${RESOLVED_DEPS})
string(FIND "${DEP}" "${LIBS_FIND_DIRS}" POS)
if(NOT POS EQUAL 0)
message(STATUS "${DEP}")
endif()
endforeach()
message(STATUS "\nUnresolved dependencies:\n")
foreach(DEP ${UNRESOLVED_DEPS})
message(STATUS "${DEP}")
endforeach()
if (EXISTS "${QT_SHARE_DIR}/plugins/platforms")
file(COPY "${QT_SHARE_DIR}/plugins/platforms" DESTINATION "${OUTPUT_DIR}")
endif()
if (EXISTS "${QT_SHARE_DIR}/plugins/platformthemes")
file(COPY "${QT_SHARE_DIR}/plugins/platformthemes" DESTINATION "${OUTPUT_DIR}")
endif()
if (EXISTS "${QT_SHARE_DIR}/plugins/styles")
file(COPY "${QT_SHARE_DIR}/plugins/styles" DESTINATION "${OUTPUT_DIR}")
endif()
if (EXISTS "${QT_SHARE_DIR}/plugins/imageformats")
file(COPY "${QT_SHARE_DIR}/plugins/imageformats" DESTINATION "${OUTPUT_DIR}")
endif()
if (USE_SQL AND (EXISTS "${QT_SHARE_DIR}/plugins/sqldrivers"))
file(COPY "${QT_SHARE_DIR}/plugins/sqldrivers" DESTINATION "${OUTPUT_DIR}")
endif()
if (USE_PRINT AND (EXISTS "${QT_SHARE_DIR}/plugins/printsupport"))
file(COPY "${QT_SHARE_DIR}/plugins/printsupport" DESTINATION "${OUTPUT_DIR}")
endif()
if (USE_NETWORK AND (EXISTS "${QT_SHARE_DIR}/plugins/networkinformation"))
file(COPY "${QT_SHARE_DIR}/plugins/networkinformation" DESTINATION "${OUTPUT_DIR}")
endif()
if (USE_MULTIMEDIA AND (EXISTS "${QT_SHARE_DIR}/plugins/multimedia"))
file(COPY "${QT_SHARE_DIR}/plugins/multimedia" DESTINATION "${OUTPUT_DIR}")
endif()
if (EXISTS "${QT_SHARE_DIR}/translations")
file(MAKE_DIRECTORY "${OUTPUT_DIR}/translations")
if (EXISTS "${QT_SHARE_DIR}/translations/qtbase_ru.qm")
file(COPY "${QT_SHARE_DIR}/translations/qtbase_ru.qm" DESTINATION "${OUTPUT_DIR}/translations")
endif()
if (EXISTS "${QT_SHARE_DIR}/translations/qtdeclarative_ru.qm")
file(COPY "${QT_SHARE_DIR}/translations/qtdeclarative_ru.qm" DESTINATION "${OUTPUT_DIR}/translations")
endif()
if (USE_SERIALPORT AND (EXISTS "${QT_SHARE_DIR}/translations/qtserialport_ru.qm"))
file(COPY "${QT_SHARE_DIR}/translations/qtserialport_ru.qm" DESTINATION "${OUTPUT_DIR}/translations")
endif()
if (USE_MULTIMEDIA AND (EXISTS "${QT_SHARE_DIR}/translations/qtmultimedia_ru.qm"))
file(COPY "${QT_SHARE_DIR}/translations/qtmultimedia_ru.qm" DESTINATION "${OUTPUT_DIR}/translations")
endif()
endif()
else()
message(STATUS "'${TARGET_FILE}' not found. Exit.")
endif()

View File

@ -0,0 +1,8 @@
if(NOT DEFINED SOFT_DEVELOPER OR "${SOFT_DEVELOPER}" STREQUAL "")
set(SOFT_DEVELOPER "Evgeny Teterin")
endif()
if(NOT DEFINED DEVELOPER_DOMAIN OR "${DEVELOPER_DOMAIN}" STREQUAL "")
set(DEVELOPER_DOMAIN "poseon.ru")
endif()

View File

@ -0,0 +1,29 @@
# Файл для подключения в основной проект через include
# Настройки для библиотек
include(${CMAKE_CURRENT_LIST_DIR}/common.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/developer.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${DISTRIB_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${DISTRIB_DIR})
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(RUNTIME_OUTPUT_NAME lib${PROJECT_NAME}.dll)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/versioninfo.rc.in
${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc
@ONLY
)
else()
set(RUNTIME_OUTPUT_NAME lib${PROJECT_NAME}.so)
endif()
configure_file(
${CMAKE_CURRENT_LIST_DIR}/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
@ONLY
)

View File

@ -0,0 +1,40 @@
# Файл для подключения в основной проект через include
# Вспомогательные действия после сборки проекта
if(IS_DEBUG)
message(STATUS "Project '${PROJECT_NAME}' [post-build] DEBUG building: skip post-build actions")
return()
endif()
# Включение/выключение поиска зависимостей:
set(COPY_DEPENDS ON)
get_target_property(TARGET_TYPE ${PROJECT_NAME} TYPE)
if(TARGET_TYPE STREQUAL "EXECUTABLE")
message(STATUS "${PROJECT_NAME} is an executable.")
elseif(TARGET_TYPE STREQUAL "STATIC_LIBRARY" OR TARGET_TYPE STREQUAL "SHARED_LIBRARY")
message(STATUS "${PROJECT_NAME} is a library.")
else()
message(STATUS "${PROJECT_NAME} is of another type: ${TARGET_TYPE}")
endif()
if (COPY_DEPENDS)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying runtime dependencies..."
COMMAND ${CMAKE_COMMAND}
-DTARGET_NAME="${RUNTIME_OUTPUT_NAME}"
-DTARGET_TYPE="${TARGET_TYPE}"
-DOUTPUT_DIR="${DISTRIB_DIR}"
-DPREFIX="${CMAKE_PREFIX_PATH}"
-DQT_VERSION_MAJOR=${QT_VERSION_MAJOR}
-DCMAKE_SYSTEM_NAME="${CMAKE_SYSTEM_NAME}"
-P "${CMAKE_INC_DIR}/copy_depends.cmake"
COMMENT "Copying runtime dependencies for ${PROJECT_NAME}"
)
endif()

View File

@ -0,0 +1,39 @@
set(ROOT_PROJECT_DIR
${CMAKE_CURRENT_LIST_DIR}/..
)
cmake_path(NORMAL_PATH ROOT_PROJECT_DIR OUTPUT_VARIABLE ROOT_PROJECT_DIR)
set(CMAKE_INC_DIR
${ROOT_PROJECT_DIR}_cmake
)
set(RESOURCES_DIR
${ROOT_PROJECT_DIR}_resources
)
set(COMMON_SOURCES_DIR
${ROOT_PROJECT_DIR}_include
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(SYSTEM_INCLUDE_DIR "${CMAKE_PREFIX_PATH}/include")
else ()
set(SYSTEM_INCLUDE_DIR "/usr/include")
endif ()
get_filename_component(CXX_COMPILER_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY)
get_filename_component(CMAKE_BIN_DIR "${CMAKE_COMMAND}" DIRECTORY)
set(ROOT_COMPILER_DIR
${CXX_COMPILER_DIR}/..
)
cmake_path(NORMAL_PATH ROOT_COMPILER_DIR OUTPUT_VARIABLE ROOT_COMPILER_DIR)
set(COMPILER_INCLUDE_DIR
${ROOT_COMPILER_DIR}include
)

View File

@ -0,0 +1,61 @@
# Файл для подключения в основной проект через include
# Настройки компиляции для всего
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-Wextra
-Wpedantic
)
if (DISABLE_HARD_WARNING_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-Wextra
-Wpedantic
)
else()
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall # Все стандартные предупреждения
-Wextra # Дополнительные предупреждения
-Wpedantic # Соответствие стандарту C++
-Werror # Превратить предупреждения в ошибки
-Wconversion # Предупреждения о неявных преобразованиях
-Wsign-conversion # Предупреждения о знаковых/беззнаковых преобразованиях
-Wshadow # Предупреждения о "тенях" переменных
-Wunused # Предупреждения о неиспользуемом коде
-Wold-style-cast # Запрет C-style кастов (только static_cast/dynamic_cast/...)
-Wnull-dereference # Предупреждения о возможных разыменованиях nullptr
-Wdouble-promotion # Предупреждения о неявном преобразовании float double
-Wformat=2 # Строгая проверка printf/scanf
)
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE
QT_NO_FOREACH
QT_NO_URL_CAST_FROM_STRING
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
QT_STRICT_ITERATORS
)
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
if (CLANG_TIDY_EXE)
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-checks=*")
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
target_compile_options(${PROJECT_NAME} PRIVATE
-fsanitize=address # AddressSanitizer (поиск утечек, выходов за границы)
-fsanitize=undefined # UndefinedBehaviorSanitizer (UB-проверки)
-fsanitize=leak # LeakSanitizer (поиск утечек памяти)
-fno-omit-frame-pointer # Для лучшего стека вызовов в санитайзерах
)
target_link_options(${PROJECT_NAME} PRIVATE
-fsanitize=address
-fsanitize=undefined
-fsanitize=leak
)
endif()

View File

@ -0,0 +1,70 @@
# Файл для подключения в основной проект через include
# Обновление и генерация файлов переводов
# Языки переводов перечислить в переменной LNG
if(IS_DEBUG)
message(STATUS "Project '${PROJECT_NAME}' [translations] DEBUG building: skip .qm generation")
return()
endif()
# Если список языков не задан в основном CMakeLists.txt применяем по умолчанию
if(NOT DEFINED LNG)
set(LNG en ru)
message(STATUS "Project '${PROJECT_NAME}' [translations] LNG not set, use default: ${LNG}")
else()
message(STATUS "Project '${PROJECT_NAME}' [translations] Use LNG: ${LNG}")
endif()
# Подключить LinguistTools
find_package(Qt${QT_VERSION_MAJOR}LinguistTools REQUIRED)
# Полные пути утилит
get_target_property(LUPDATE_EXECUTABLE Qt${QT_VERSION_MAJOR}::lupdate IMPORTED_LOCATION)
get_target_property(LRELEASE_EXECUTABLE Qt${QT_VERSION_MAJOR}::lrelease IMPORTED_LOCATION)
# Каталог генерации файлов ts и qm файлов
set(TRANSLATIONS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/translations")
set(QM_OUTPUT_DIR "${DISTRIB_DIR}/translations")
file(MAKE_DIRECTORY "${TRANSLATIONS_DIR}")
file(MAKE_DIRECTORY "${QM_OUTPUT_DIR}")
# Список TS файлов в зависимости от списка языков
set(TS_FILES "")
# Обновление каждого файла с добавлением в список
foreach(LANG ${LNG})
set(TS_FILE "${TRANSLATIONS_DIR}/${PROJECT_NAME}_${LANG}.ts")
list(APPEND TS_FILES "${TS_FILE}")
message(STATUS "Project '${PROJECT_NAME}' Update translation file: ${TS_FILE}")
execute_process(COMMAND ${LUPDATE_EXECUTABLE}
${PROJECT_SOURCES}
-ts ${TS_FILE}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endforeach()
# Генерация файлов qm
set(QM_FILES "")
foreach(TS ${TS_FILES})
get_filename_component(TS_NAME_WE ${TS} NAME_WE)
set(QM ${QM_OUTPUT_DIR}/${TS_NAME_WE}.qm)
list(APPEND QM_FILES ${QM})
add_custom_command(
OUTPUT ${QM}
COMMAND ${LRELEASE_EXECUTABLE} ${TS} -qm ${QM}
DEPENDS ${TS}
COMMENT "Generating ${QM}"
)
endforeach()
add_custom_target(translations_qm ALL DEPENDS ${QM_FILES})
add_dependencies(translations_qm ${PROJECT_NAME})

View File

@ -0,0 +1,52 @@
# Variables for generating the version.
# Used in the application (config.h file and versioninfo.rc)
#
# Получаем полный временной штамп в UTC (пример: "2025-04-16 12:33:58 UTC")
string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S UTC" UTC)
string(TIMESTAMP VERSION_DOY "%j" UTC) # день в году (001..366)
# Извлекаем компоненты даты и времени
string(SUBSTRING "${BUILD_DATE}" 0 4 YEAR_STR)
string(SUBSTRING "${BUILD_DATE}" 2 2 VERSION_YY)
string(SUBSTRING "${BUILD_DATE}" 5 2 VERSION_MM)
string(SUBSTRING "${BUILD_DATE}" 8 2 VERSION_DD)
string(SUBSTRING "${BUILD_DATE}" 11 2 VERSION_HH)
string(SUBSTRING "${BUILD_DATE}" 14 2 VERSION_MIN)
string(SUBSTRING "${BUILD_DATE}" 17 2 VERSION_SS)
# Убираем ведущие нули путём преобразования в числа
math(EXPR VERSION_YY_NOZERO "${VERSION_YY}")
math(EXPR VERSION_MM_NOZERO "${VERSION_MM}")
math(EXPR VERSION_DD_NOZERO "${VERSION_DD}")
math(EXPR VERSION_HH_NOZERO "${VERSION_HH}")
math(EXPR VERSION_MIN_NOZERO "${VERSION_MIN}")
math(EXPR VERSION_SS_NOZERO "${VERSION_SS}")
math(EXPR VERSION_DOY_NOZERO "${VERSION_DOY}")
if (VERSION_FULLDATE)
# Полная дата: YY.MM.DD.NNN
# Считаем количество минут с начала суток
math(EXPR VERSION_NNN
"${VERSION_HH_NOZERO} * 60 + ${VERSION_MIN_NOZERO}"
)
set(PROJECT_VERSION_MAJOR ${VERSION_YY_NOZERO})
set(PROJECT_VERSION_MINOR ${VERSION_MM_NOZERO})
set(PROJECT_VERSION_PATCH ${VERSION_DD_NOZERO})
set(BUILD_NUM ${VERSION_NNN})
else()
# Версия от MAJOR, MINOR + YY + день года
# MAJOR и MINOR должны быть заданы ранее через `project(... VERSION ...)`
set(PROJECT_VERSION_PATCH ${VERSION_YY_NOZERO})
set(BUILD_NUM ${VERSION_DOY_NOZERO})
endif()
set(ORIGINAL_FILE_NAME ${RUNTIME_OUTPUT_NAME})
message(STATUS "Project '${PROJECT_NAME}' version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${BUILD_NUM}")

View File

@ -0,0 +1,35 @@
1 TYPELIB "versioninfo.rc"
1 VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, @BUILD_NUM@
PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, @BUILD_NUM@
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "@SOFT_DEVELOPER@"
VALUE "FileDescription", "@PROJECT_DESCRIPTION@"
VALUE "FileVersion","@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUM@"
VALUE "InternalName", "@PROJECT_NAME@"
VALUE "LegalCopyright", "Copyright (c) @YEAR_STR@ @SOFT_DEVELOPER@"
VALUE "OriginalFilename", "@ORIGINAL_FILE_NAME@"
VALUE "ProductName", "@PROJECT_NAME@"
VALUE "ProductVersion","@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUM@"
VALUE "BuildDate", "@BUILD_DATE@"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View File

@ -0,0 +1,106 @@
/****************************************************************************
** Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#include "application_config.h"
#include "config.h"
#include <QtWidgets/QApplication>
#include <QtCore/QTranslator>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QStringList>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
# include <QtCore/QTextCodec>
#endif
//==============================================================================
void Application::initialize()
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
#endif
#if defined (PROG_NAME)
QApplication::setApplicationName( QString(PROG_NAME) );
#endif
#if defined (SOFT_DEVELOPER)
QApplication::setOrganizationName( QString(SOFT_DEVELOPER) );
#endif
#if defined (DEVELOPER_DOMAIN)
QApplication::setOrganizationDomain( QString(DEVELOPER_DOMAIN) );
#endif
#if defined (PROG_VERSION)
QApplication::setApplicationVersion( QString(PROG_VERSION) );
#endif
#if defined (BUILD_DATE)
qApp->setProperty( "buildDate", QString(BUILD_DATE) );
#endif
}
//==============================================================================
QString Application::applicationRootPath()
{
QString path { QApplication::applicationDirPath() };
if ( path.right(1) != "/" ) {
path += "/";
}
if (path.right(5) == "/bin/") {
path.remove( path.length() - 4, 4 );
}
return path;
}
//==============================================================================
void Application::installTranslations(const QString &lng)
{
const QString dirName { applicationRootPath() + "translations/" };
QDir translationsDir { dirName };
QStringList filesList = translationsDir.entryList(
QStringList() << QString("*_%1.qm").arg(lng),
QDir::Files
);
for (const QString &fileName: std::as_const(filesList)) {
QTranslator *translator = new QTranslator(QApplication::instance());
if (translator->load( dirName + fileName )) {
QApplication::instance()->installTranslator( translator );
}
else {
delete translator;
}
}
}
//==============================================================================

View File

@ -0,0 +1,46 @@
/****************************************************************************
** Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#pragma once
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
#include <QtCore/QString>
//==============================================================================
class Application
{
Q_DISABLE_COPY(Application)
public:
static void initialize();
static QString applicationRootPath();
static void installTranslations(const QString &lng = "ru");
private:
Application() = delete;
};
#endif // APP_CONFIG_H
//==============================================================================

View File

@ -0,0 +1,12 @@
<RCC>
<qresource prefix="/">
<file>icons/cancel-40.png</file>
<file>icons/change-theme-40.png</file>
<file>icons/close-40.png</file>
<file>icons/ok-40.png</file>
<file>icons/opened-folder-40.png</file>
<file>icons/restart-40.png</file>
<file>icons/save-as-40.png</file>
<file>icons/shutdown-40.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
_resources/main.ico 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>images/main_icon.png</file>
<file>images/main_title.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1 @@
IDI_ICON1 ICON "main.ico"

View File

@ -0,0 +1,87 @@
cmake_minimum_required(VERSION 4.0)
project(opencv_example
VERSION 1.0
DESCRIPTION "Example OpenCV"
LANGUAGES CXX
)
include(${CMAKE_CURRENT_SOURCE_DIR}/../_cmake/app_settings.cmake)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS
Core Widgets
)
find_package(OpenCV REQUIRED)
set(PROJECT_SOURCES
main.cpp
main_window.cpp
main_window.h
main_window.ui
${COMMON_SOURCES_DIR}/application_config.h
${COMMON_SOURCES_DIR}/application_config.cpp
)
set(PROJECT_RESOURCES
${RESOURCES_DIR}/main.qrc
${RESOURCES_DIR}/icons.qrc
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(PROJECT_RC_FILES
${RESOURCES_DIR}/main_icon.rc
${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc
)
endif()
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(${PROJECT_NAME}
MANUAL_FINALIZATION
${PROJECT_SOURCES}
${PROJECT_RESOURCES}
${PROJECT_RC_FILES}
)
else()
add_executable(${PROJECT_NAME}
${PROJECT_SOURCES}
${PROJECT_RESOURCES}
${PROJECT_RC_FILES}
)
endif()
target_link_directories(${PROJECT_NAME} PRIVATE
${DISTRIB_DIR}
${CXX_COMPILER_DIR}
)
include(${CMAKE_INC_DIR}/target_options.cmake)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Widgets
${OpenCV_LIBS}
)
target_include_directories(${PROJECT_NAME} PRIVATE
${SYSTEM_INCLUDE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${COMMON_SOURCES_DIR}
${COMPILER_INCLUDE_DIR}
${OpenCV_INCLUDE_DIRS}
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set_target_properties(${PROJECT_NAME} PROPERTIES
WIN32_EXECUTABLE TRUE
)
endif()
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(${PROJECT_NAME})
endif()
include(${CMAKE_INC_DIR}/post_build.cmake)

21
sources/main.cpp 100644
View File

@ -0,0 +1,21 @@
#include "main_window.h"
#include <QtWidgets/QApplication>
#include "application_config.h"
//==============================================================================
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Application::initialize();
Application::installTranslations();
MainWindow w;
w.show();
a.setQuitOnLastWindowClosed(true);
return a.exec();
}
//==============================================================================

View File

@ -0,0 +1,819 @@
/****************************************************************************
** Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#include "main_window.h"
#include "./ui_main_window.h"
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QLabel>
#include <QtCore/QDebug>
#include <QtGui/QPixmap>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QFileDialog>
#include <QtGui/QResizeEvent>
#include <QtWidgets/QMessageBox>
#include <math.h>
#include <float.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/core/types.hpp>
//==============================================================================
// Функции обработки изображений OpenCV
//==============================================================================
cv::Mat qImageToMat(const QImage &image)
{
qDebug() << "qImageToMat";
cv::Mat result;
if (image.isNull()) return result;
QImage img = image.convertToFormat( QImage::Format_RGB888 );
cv::Mat tmp(img.height(), img.width(), CV_8UC3,
const_cast<uchar*>(img.bits()),
static_cast<size_t>(img.bytesPerLine()));
try {
cv::cvtColor(tmp, result, CV_BGR2RGB);
} catch (...) {
return cv::Mat();
}
return result.clone();
}
//==============================================================================
QImage matToQImage(const cv::Mat &mat)
{
qDebug() << "matToQImage";
if(mat.empty()) {
return QImage();
}
cv::Mat temp;
try {
cv::cvtColor(mat, temp, CV_BGR2RGB);
} catch (...) {
return QImage();
}
if(temp.empty()) {
return QImage();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QImage dest( const_cast<uchar *>(temp.data),
temp.cols, temp.rows,
static_cast<qsizetype>(temp.step), QImage::Format_RGB888);
#else
QImage dest( const_cast<uchar *>(temp.data),
temp.cols, temp.rows,
static_cast<int>(temp.step), QImage::Format_RGB888);
#endif
if(dest.isNull()) return QImage();
return dest.copy();
}
//==============================================================================
cv::Mat applyGain(const cv::Mat &inputImage, double gain)
{
qDebug() << "applyGain";
cv::Mat outputImage;
if (inputImage.empty()) return outputImage;
try {
// Умножаем каждый пиксель на коэффициент усиления
inputImage.convertTo(outputImage, -1, gain, 0);
cv::threshold(outputImage, outputImage, 255, 255, cv::THRESH_TRUNC);
}
catch (...) {
outputImage = cv::Mat();
}
return outputImage;
}
//==============================================================================
cv::Mat adjustBrightnessContrast(const cv::Mat &img, double alpha, int beta)
{
qDebug() << "adjustBrightnessContrast";
cv::Mat adjusted;
if (img.empty()) return adjusted;
try {
img.convertTo(adjusted, -1, alpha, beta); // alpha - контраст, beta - яркость
cv::threshold(adjusted, adjusted, 255, 255, cv::THRESH_TRUNC);
}
catch (...) {
adjusted = cv::Mat();
}
return adjusted;
}
//==============================================================================
cv::Mat equalizeColorHist(const cv::Mat &bgrImg)
{
qDebug() << "equalizeColorHist";
cv::Mat result;
if (bgrImg.empty()) return result;
try {
cv::Mat ycrcb;
cv::cvtColor(bgrImg, ycrcb, cv::COLOR_BGR2YCrCb);
std::vector<cv::Mat> channels;
cv::split(ycrcb, channels);
cv::equalizeHist(channels[0], channels[0]); // Эквализация только по Y (яркость)
cv::merge(channels, ycrcb);
cv::cvtColor(ycrcb, result, cv::COLOR_YCrCb2BGR);
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
cv::Mat sharpenLaplacian(const cv::Mat &img)
{
qDebug() << "sharpenLaplacian";
cv::Mat result;
if (img.empty()) return result;
try {
cv::Mat sharpened;
cv::Laplacian(img, sharpened, CV_8U, 3);
result = img - 0.5 * sharpened; // Можно регулировать силу эффекта
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
cv::Mat unsharpMask(const cv::Mat &img, double sigma, double amount)
{
qDebug() << "unsharpMask";
cv::Mat result;
if (img.empty()) return result;
try {
cv::Mat blurred;
cv::GaussianBlur(img, blurred, cv::Size(0, 0), sigma);
result = img * (1 + amount) - blurred * amount;
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
cv::Mat denoiseMedian(const cv::Mat &img, int kernelSize = 3)
{
qDebug() << "denoiseMedian";
cv::Mat denoised;
if (img.empty()) return denoised;
try {
cv::medianBlur(img, denoised, (kernelSize % 2 == 0) ? kernelSize + 1 : kernelSize);
}
catch (...) {
denoised = cv::Mat();
}
return denoised;
}
//==============================================================================
cv::Mat denoiseNlm(const cv::Mat &img, float h = 10)
{
qDebug() << "denoiseNLM";
cv::Mat denoised;
if (img.empty()) return denoised;
try {
cv::fastNlMeansDenoisingColored(img, denoised, h);
}
catch (...) {
denoised = cv::Mat();
}
return denoised;
}
//==============================================================================
cv::Mat autoExposure(const cv::Mat &img)
{
qDebug() << "autoExposure";
cv::Mat result;
if (img.empty()) return result;
try {
cv::Mat lab;
cv::cvtColor(img, lab, cv::COLOR_BGR2Lab);
std::vector<cv::Mat> channels;
cv::split(lab, channels);
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
clahe->setClipLimit(2.0);
clahe->apply(channels[0], channels[0]); // Применяем CLAHE к каналу L
cv::merge(channels, lab);
cv::cvtColor(lab, result, cv::COLOR_Lab2BGR);
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
cv::Mat grayWorldWb(const cv::Mat &img)
{
qDebug() << "grayWorldWB";
cv::Mat balanced;
if (img.empty()) return balanced;
try {
cv::Scalar avg = cv::mean(img);
double avgGray = (avg[0] + avg[1] + avg[2]) / 3;
balanced = img.clone();
balanced.forEach<cv::Vec3b>([&](cv::Vec3b &pixel, const int*) {
pixel[0] = cv::saturate_cast<uchar>(pixel[0] * avgGray / avg[0]);
pixel[1] = cv::saturate_cast<uchar>(pixel[1] * avgGray / avg[1]);
pixel[2] = cv::saturate_cast<uchar>(pixel[2] * avgGray / avg[2]);
});
}
catch (...) {
balanced = cv::Mat();
}
return balanced;
}
//==============================================================================
cv::Mat enhanceColors(const cv::Mat &img, float saturationFactor = 1.5)
{
qDebug() << "enhanceColors";
cv::Mat result;
if (img.empty()) return result;
try {
cv::Mat hsv;
cv::cvtColor(img, hsv, cv::COLOR_BGR2HSV);
hsv.forEach<cv::Vec3b>([&](cv::Vec3b &pixel, const int*) {
pixel[1] = cv::saturate_cast<uchar>(pixel[1] * saturationFactor); // Усиливаем насыщенность
});
cv::cvtColor(hsv, result, cv::COLOR_HSV2BGR);
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
cv::Mat removeVignette(const cv::Mat &img, double strength = 0.5)
{
qDebug() << "removeVignette";
cv::Mat result;
if (img.empty()) return result;
try {
cv::Mat mask(img.size(), CV_32F);
int centerX = img.cols / 2;
int centerY = img.rows / 2;
double radius = std::sqrt(centerX * centerX + centerY * centerY);
for (int y = 0; y < img.rows; ++y) {
for (int x = 0; x < img.cols; ++x) {
double dist = std::sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY));
double factor = 1.0 - strength * (dist / radius);
mask.at<float>(y, x) = static_cast<float>( std::max(factor, 0.1) ); // Чтобы не было деления на 0
}
}
cv::Mat imgFloat;
img.convertTo(imgFloat, CV_32F);
std::vector<cv::Mat> channels;
cv::split(imgFloat, channels);
for (auto &channel : channels) {
channel = channel / mask;
}
cv::merge(channels, imgFloat);
imgFloat.convertTo(result, CV_8U);
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
cv::Mat applyClache(const cv::Mat &img)
{
qDebug() << "applyCLAHE";
cv::Mat result;
if (img.empty()) return result;
try {
cv::Mat lab;
cv::cvtColor(img, lab, cv::COLOR_BGR2Lab);
std::vector<cv::Mat> channels;
cv::split(lab, channels);
cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
clahe->setClipLimit(2.0);
clahe->apply(channels[0], channels[0]);
cv::merge(channels, lab);
cv::cvtColor(lab, result, cv::COLOR_Lab2BGR);
}
catch (...) {
result = cv::Mat();
}
return result;
}
//==============================================================================
// Форма просмотра изображения
//==============================================================================
ImageViewer::ImageViewer(const QImage &image, QWidget *parent) : QWidget(parent)
{
setWindowFlags( windowFlags() | Qt::Dialog);
setAttribute(Qt::WA_DeleteOnClose, true);
setMinimumSize(300, 200);
labelImage = image;
label = new QLabel(this);
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
label->setAlignment(Qt::AlignCenter);
label->setMinimumSize(1, 1); // Позволяет уменьшать окно
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(label);
QHBoxLayout *hLayout = new QHBoxLayout();
hLayout->addStretch();
QPushButton *btn = new QPushButton(this);
btn->setText(tr("Сохранить как..."));
connect(btn, &QPushButton::clicked, this, &ImageViewer::save);
hLayout->addWidget(btn);
layout->addLayout(hLayout);
setLayout(layout);
updateImage();
}
//==============================================================================
void ImageViewer::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateImage();
}
//==============================================================================
void ImageViewer::updateImage()
{
if (!label) return;
label->clear();
if (!labelImage.isNull()) {
QPixmap pixmap = QPixmap::fromImage(labelImage);
label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
}
//==============================================================================
void ImageViewer::save() {
QString fileName = QFileDialog::getSaveFileName(
this, tr("Сохранить изображение"),
QString(),
QString("Изображения png (*.png)")
);
if (fileName.isEmpty()) return;
if (labelImage.save(fileName)) {
QMessageBox::information(this, tr("Игформация"), tr("Файл сохранён"));
}
else {
QMessageBox::critical(this, tr("Ошибка"), tr("Ошибка при сохранении файла"));
}
}
//==============================================================================
//==============================================================================
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
initialize();
updateControls();
actionsEnabled = true;
}
//==============================================================================
MainWindow::~MainWindow()
{
delete ui;
}
//==============================================================================
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
updateLabelImage();
}
//==============================================================================
void MainWindow::initialize()
{
QString title = qGuiApp->applicationDisplayName().isEmpty()
? qGuiApp->applicationName()
: qGuiApp->applicationDisplayName();
if (!title.isEmpty()) this->setWindowTitle( title );
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpen);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onActionSaveAs);
connect(ui->actionShowImage, &QAction::triggered, this, &MainWindow::onActionShowImage);
connect(ui->actionUndo, &QAction::triggered, this, &MainWindow::onActionUndo);
connect(ui->actionReset, &QAction::triggered, this, &MainWindow::onActionReset);
connect(ui->pushButtonGain, &QPushButton::clicked, this, &MainWindow::btnGain);
connect(ui->pushButtonBrightnessAndContrast, &QPushButton::clicked, this, &MainWindow::btnBrightnessAndContrast);
connect(ui->pushButtonHistogramEqual, &QPushButton::clicked, this, &MainWindow::btnHistogramEqual);
connect(ui->pushButtonLaplacian, &QPushButton::clicked, this, &MainWindow::btnLaplacian);
connect(ui->pushButtonUnsharpMask, &QPushButton::clicked, this, &MainWindow::btnUnsharpMask);
connect(ui->pushButtonMedian, &QPushButton::clicked, this, &MainWindow::btnMedian);
connect(ui->pushButtonFastNlMeans, &QPushButton::clicked, this, &MainWindow::btnFastNlMeans);
connect(ui->pushButtonAutoExposure, &QPushButton::clicked, this, &MainWindow::btnAutoExposure);
connect(ui->pushButtonWhiteBalance, &QPushButton::clicked, this, &MainWindow::btnWhiteBalance);
connect(ui->pushButtonSaturation, &QPushButton::clicked, this, &MainWindow::btnSaturation);
connect(ui->pushButtonVinete, &QPushButton::clicked, this, &MainWindow::btnVinete);
connect(ui->pushButtonClahe, &QPushButton::clicked, this, &MainWindow::btnClahe);
ui->labelImage->setMinimumSize(1, 1);
ui->scrollArea->verticalScrollBar()->setValue(0);
imgList.reserve(maximumImagesCount);
}
//==============================================================================
cv::Mat MainWindow::prepareImage(const QString &operationName)
{
if (imgList.isEmpty()) return cv::Mat();
qDebug() << operationName;
return qImageToMat(imgList.last());
}
//==============================================================================
void MainWindow::finallyImage(const cv::Mat &output)
{
if (output.empty()) {
qCritical() << "Ошибка при выполнении";
}
else {
imgList.append( matToQImage(output) );
while (imgList.size() > maximumImagesCount) imgList.removeAt(1);
}
updateLabelImage();
updateControls();
if (output.empty()) {
QMessageBox::critical(this, tr("Ошибка"), tr("Ошибка при выполнении"));
}
}
//==============================================================================
void MainWindow::updateLabelImage()
{
ui->labelImage->clear();
QImage currentImage;
if (!imgList.isEmpty()) currentImage = imgList.last();
if (currentImage.isNull()) {
ui->labelImage->setText(tr("Изображение не загружено"));
return;
}
QPixmap pixmap = QPixmap::fromImage(currentImage);
ui->labelImage->setPixmap(
pixmap.scaled(ui->labelImage->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)
);
}
//==============================================================================
void MainWindow::updateControls()
{
bool imageExist = !imgList.isEmpty() && !imgList.last().isNull();
ui->widget->setEnabled( imageExist );
ui->actionShowImage->setEnabled( imageExist );
ui->actionSave->setEnabled( imageExist );
ui->actionUndo->setEnabled( imgList.size() > 1 );
ui->actionReset->setEnabled( imgList.size() > 1 );
}
//==============================================================================
void MainWindow::onActionOpen()
{
QString fileName = QFileDialog::getOpenFileName(
this, tr("Открыть изображение"),
QString(),
QString("Файлы изображений (*.png *.jpg *.jpeg)")
);
if (fileName.isEmpty()) return;
qDebug() << "Загрузка из файла: " << fileName;
ui->labelImage->clear();
imgList.clear();
QImage tmpImage( fileName );
if (tmpImage.isNull()) {
qCritical() << "После загрузки изображение isNull";
}
if (!tmpImage.isNull()) {
imgList.append(tmpImage);
}
updateLabelImage();
updateControls();
if (tmpImage.isNull()) {
qCritical() << "Ошибка при загрузке";
QMessageBox::critical(this, tr("Ошибка"), tr("Ошибка при загрузке файла"));
}
}
//==============================================================================
void MainWindow::onActionSaveAs()
{
if (imgList.isEmpty()) return;
QString fileName = QFileDialog::getSaveFileName(
this, tr("Сохранить изображение"),
QString(),
QString("Изображения png (*.png)")
);
if (fileName.isEmpty()) return;
qDebug() << "Сохранение в файл: " << fileName;
if (imgList.last().save(fileName)) {
QMessageBox::information(this, tr("Информация"), tr("Файл сохранён"));
}
else {
qCritical() << "Ошибка при сохранении";
QMessageBox::critical(this, tr("Ошибка"), tr("Ошибка при сохранении файла"));
}
}
//==============================================================================
void MainWindow::onActionShowImage()
{
if (imgList.isEmpty()) return;
ImageViewer *w = new ImageViewer(imgList.last(), this);
w->show();
}
//==============================================================================
void MainWindow::onActionUndo()
{
if (imgList.size() > 1) imgList.removeLast();
updateLabelImage();
updateControls();
}
//==============================================================================
void MainWindow::onActionReset()
{
while (imgList.size() > 1) imgList.removeLast();
updateLabelImage();
updateControls();
}
//==============================================================================
void MainWindow::btnGain()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Простое усиление яркости");
cv::Mat output = applyGain(input, ui->doubleSpinBoxGain->value());
finallyImage(output);
}
//==============================================================================
void MainWindow::btnBrightnessAndContrast()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Яркость и контраст");
cv::Mat output = adjustBrightnessContrast(input,
ui->doubleSpinBoxBrightness->value(),
ui->spinBoxContrast->value());
finallyImage(output);
}
//==============================================================================
void MainWindow::btnHistogramEqual()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Гистограммная эквализация");
cv::Mat output = equalizeColorHist(input);
finallyImage(output);
}
//==============================================================================
void MainWindow::btnLaplacian()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Фильтр Лапласа");
cv::Mat output = sharpenLaplacian(input);
finallyImage(output);
}
//==============================================================================
void MainWindow::btnUnsharpMask()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Размытие + вычитание");
cv::Mat output = unsharpMask(input, ui->doubleSpinBoxSigma->value(),
ui->doubleSpinBoxAmount->value() );
finallyImage(output);
}
//==============================================================================
void MainWindow::btnMedian()
{
if (imgList.isEmpty()) return;
if (ui->spinBoxMedian->value() % 2 == 0)
ui->spinBoxMedian->setValue( ui->spinBoxMedian->value() + 1 );
cv::Mat input = prepareImage("Медианный фильтр");
cv::Mat output = denoiseMedian(input, ui->spinBoxMedian->value() );
finallyImage(output);
}
//==============================================================================
void MainWindow::btnFastNlMeans()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("OpenCV FastNlMeans");
cv::Mat output = denoiseNlm(input, static_cast<float>(ui->doubleSpinBoxFastNlMeans->value()) );
finallyImage(output);
}
//==============================================================================
void MainWindow::btnAutoExposure()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Автокорректировка экспозиции");
cv::Mat output = autoExposure(input);
finallyImage(output);
}
//==============================================================================
void MainWindow::btnWhiteBalance()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Баланс белого");
cv::Mat output = grayWorldWb(input);
finallyImage(output);
}
//==============================================================================
void MainWindow::btnSaturation()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Цветовая насыщенность");
cv::Mat output = denoiseNlm(input, static_cast<float>(ui->doubleSpinBoxSaturation->value()) );
finallyImage(output);
}
//==============================================================================
void MainWindow::btnVinete()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Коррекция виньетирования");
cv::Mat output = removeVignette(input, ui->doubleSpinBoxVinete->value() );
finallyImage(output);
}
//==============================================================================
void MainWindow::btnClahe()
{
if (imgList.isEmpty()) return;
cv::Mat input = prepareImage("Адаптивное выравнивание гистограммы");
cv::Mat output = applyClache(input);
finallyImage(output);
}
//==============================================================================

View File

@ -0,0 +1,120 @@
/****************************************************************************
** Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#pragma once
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets/QMainWindow>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtGui/QImage>
#include <QtWidgets/QWidget>
#include <opencv2/core/mat.hpp>
//==============================================================================
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
class QLabel;
class QResizeEvent;
QT_END_NAMESPACE
//==============================================================================
// Форма для показа изображения в отдельном окне
//==============================================================================
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(const QImage &image, QWidget *parent = nullptr);
protected:
void resizeEvent(QResizeEvent *event) override;
private:
QLabel *label;
QImage labelImage;
//
void updateImage();
private slots:
void save();
};
//==============================================================================
// Главное окно
//==============================================================================
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void resizeEvent(QResizeEvent *event) override;
private:
const int maximumImagesCount {15};
Ui::MainWindow *ui;
bool actionsEnabled {false};
QList<QImage> imgList;
//
void initialize();
cv::Mat prepareImage(const QString &operationName);
void finallyImage(const cv::Mat &output);
private slots:
void updateControls();
void updateLabelImage();
//
void onActionOpen();
void onActionSaveAs();
void onActionShowImage();
void onActionUndo();
void onActionReset();
//
void btnGain();
void btnBrightnessAndContrast();
void btnHistogramEqual();
void btnLaplacian();
void btnUnsharpMask();
void btnMedian();
void btnFastNlMeans();
void btnAutoExposure();
void btnWhiteBalance();
void btnSaturation();
void btnVinete();
void btnClahe();
};
#endif // MAINWINDOW_H
//==============================================================================

View File

@ -0,0 +1,693 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1085</width>
<height>686</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="windowIcon">
<iconset resource="../_resources/main.qrc">
<normaloff>:/images/main_icon.png</normaloff>:/images/main_icon.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>284</width>
<height>1300</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>1300</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox_7">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Корректировка</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QPushButton" name="pushButtonAutoExposure">
<property name="text">
<string>Автокорректировка экспозиции</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonWhiteBalance">
<property name="text">
<string>Баланс белого</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonHistogramEqual">
<property name="text">
<string>Гистограммная эквализация</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonLaplacian">
<property name="text">
<string>Резкость - Фильтр Лапласа</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonClahe">
<property name="text">
<string>Адаптивное выравнивание</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxGain">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Простая регулировка яркости</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,1">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Множитель:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBoxGain">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.500000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&gt; 1 - увеличение яркости</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt; 1 - уменьшение яркости</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonGain">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Яркость и контраст</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,1">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Яркость:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBoxBrightness">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.500000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>&gt; 1 - увеличение яркости</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>&lt; 1 - уменьшение яркости</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,1">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Контраст:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxContrast">
<property name="minimum">
<number>-1000</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>&gt; 0 - увеличение контраста</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>&lt; 0 - уменьшение контраста</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonBrightnessAndContrast">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Резкость - Размытие и вычитание</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,1">
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Сигма:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBoxSigma">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>3.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Вычитание:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBoxAmount">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>2.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonUnsharpMask">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Удаление шума - Медианный фильтр</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,1">
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>Параметр k:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxMedian">
<property name="minimum">
<number>1</number>
</property>
<property name="singleStep">
<number>2</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonMedian">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Удаление шума - OpenCV FastNlMeans</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>Параметр h:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBoxFastNlMeans">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>10.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonFastNlMeans">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Улучшение цветовой насыщенности</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,1">
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>Сатурация:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBoxSaturation">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>1.500000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonSaturation">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Коррекция виньетирования</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8" stretch="1,1">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>Сила коррекции:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBoxVinete">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.200000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButtonVinete">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="labelImage">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Изображение не загружено</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1085</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>Файл</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>Правка</string>
</property>
<addaction name="actionUndo"/>
<addaction name="actionReset"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>Вид</string>
</property>
<addaction name="actionShowImage"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="actionUndo"/>
<addaction name="actionReset"/>
<addaction name="actionShowImage"/>
</widget>
<action name="actionOpen">
<property name="icon">
<iconset resource="../_resources/icons.qrc">
<normaloff>:/icons/opened-folder-40.png</normaloff>:/icons/opened-folder-40.png</iconset>
</property>
<property name="text">
<string>Открыть...</string>
</property>
</action>
<action name="actionSave">
<property name="icon">
<iconset resource="../_resources/icons.qrc">
<normaloff>:/icons/save-as-40.png</normaloff>:/icons/save-as-40.png</iconset>
</property>
<property name="text">
<string>Сохранить как...</string>
</property>
</action>
<action name="actionExit">
<property name="icon">
<iconset resource="../_resources/icons.qrc">
<normaloff>:/icons/shutdown-40.png</normaloff>:/icons/shutdown-40.png</iconset>
</property>
<property name="text">
<string>Выход</string>
</property>
</action>
<action name="actionUndo">
<property name="icon">
<iconset resource="../_resources/icons.qrc">
<normaloff>:/icons/restart-40.png</normaloff>:/icons/restart-40.png</iconset>
</property>
<property name="text">
<string>Отменить последнее преобразование</string>
</property>
</action>
<action name="actionReset">
<property name="icon">
<iconset resource="../_resources/icons.qrc">
<normaloff>:/icons/close-40.png</normaloff>:/icons/close-40.png</iconset>
</property>
<property name="text">
<string>Отменить все (загрузить исходный)</string>
</property>
</action>
<action name="actionShowImage">
<property name="icon">
<iconset resource="../_resources/icons.qrc">
<normaloff>:/icons/change-theme-40.png</normaloff>:/icons/change-theme-40.png</iconset>
</property>
<property name="text">
<string>Показать изображение в отдельном окне</string>
</property>
</action>
</widget>
<resources>
<include location="../_resources/main.qrc"/>
<include location="../_resources/icons.qrc"/>
</resources>
<connections/>
</ui>