├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── ImageTools.iss
├── ImageTools.py
├── Readme.html
├── Readme.md
├── buildexe.bat
├── components
├── BasicImage.py
├── __init__.py
├── check_update.py
├── customwidget.py
├── help_doc.py
├── histview.py
├── logconfig.py
├── property.py
├── resource.qrc
├── resource
│ ├── add.png
│ ├── compare.png
│ ├── config.png
│ ├── delete.png
│ ├── down.png
│ ├── main.ico
│ ├── main.png
│ ├── open.png
│ ├── pause.png
│ ├── restart .png
│ ├── right.png
│ ├── save_icon.png
│ ├── start.png
│ ├── stats.png
│ ├── stop.png
│ ├── subtract.png
│ ├── up.png
│ ├── 快进.png
│ └── 快退.png
├── status_code_enum.py
├── ui
│ ├── check_update_win.py
│ ├── check_update_win.ui
│ ├── help_window.py
│ ├── help_window.ui
│ ├── histgramview.py
│ ├── histgramview.ui
│ ├── mainwindow.py
│ └── mainwindow.ui
└── window.py
├── requirements.txt
├── resource_rc.py
├── subapps
├── FocusDepthTool.iss
├── FocusDepthTool.py
├── PQtools2Code.iss
├── PQtools2Code.py
├── ShakeTestTool.iss
├── ShakeTestTool.py
├── VideoCompare.iss
├── VideoCompare.py
└── __init__.py
├── test
├── __init__.py
├── components
│ ├── __init__.py
│ ├── test_basic_image.py
│ └── test_check_update.py
└── resource
│ ├── 1.jpg
│ ├── 2.jpg
│ ├── 3.png
│ ├── 6月壁纸.jpg
│ └── 星空背景 绘画 夜空 4k壁纸_彼岸图网.jpg
├── tools
├── __init__.py
├── depth_of_focus
│ ├── LenParameters.py
│ ├── Readme.md
│ ├── __init__.py
│ ├── depth_of_focus.py
│ ├── field_depth_window.py
│ └── field_depth_window.ui
├── imageeditor
│ ├── Readme.md
│ ├── __init__.py
│ ├── imageeditor.py
│ ├── imageeffect.py
│ └── ui
│ │ ├── imageeditor_window.py
│ │ ├── imageeditor_window.ui
│ │ ├── watermarkview.py
│ │ └── watermarkview.ui
├── pqtools_to_code
│ ├── pqtools2code_window.py
│ ├── pqtools2code_window.ui
│ └── pqtools_to_code.py
├── rawimageeditor
│ ├── RawImageEditor.py
│ ├── RawImageInfo.py
│ ├── RawImageParams.py
│ ├── Readme.md
│ ├── __init__.py
│ ├── debayer.py
│ ├── isp.py
│ ├── ispfunction.py
│ ├── isppipeline.py
│ ├── ui
│ │ ├── __init__.py
│ │ ├── rawimageeditor_window.py
│ │ └── rawimageeditor_window.ui
│ └── utility.py
├── shake_test
│ ├── Readme.md
│ ├── rtspconfigview.py
│ ├── rtspconfigview.ui
│ ├── shake_test.py
│ ├── shake_test_window.py
│ └── shake_test_window.ui
├── video_compare
│ ├── Readme.md
│ ├── rtspconfigview.py
│ ├── rtspconfigview.ui
│ ├── shake_test_window.py
│ ├── shake_test_window.ui
│ ├── video_pre_settings.py
│ ├── video_pre_settings.ui
│ ├── videocompare.py
│ └── videoview.py
└── yuv_viewer
│ ├── Readme.md
│ ├── __init__.py
│ ├── ui
│ ├── yuvconfig.py
│ ├── yuvconfig.ui
│ ├── yuvviewer_window.py
│ └── yuvviewer_window.ui
│ ├── yuv_config.py
│ └── yuv_viewer.py
└── version.md
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | *.spec
4 | __pycache__/
5 | *.pyc
6 | settings.json
7 | *.mp4
8 | *.jpg
9 | config/
10 | *.exe
11 | *.raw
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Python: 当前文件",
9 | "type": "python",
10 | "request": "launch",
11 | "program": "${workspaceFolder}/ImageTools.py",
12 | "console": "integratedTerminal"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "C:\\Users\\qinxing\\AppData\\Local\\Programs\\Python\\Python39\\python.exe",
3 | // 默认对Python文件进行静态检查
4 | "python.linting.enabled": true,
5 |
6 | // 默认在Python文件保存时进行静态检查
7 | "python.linting.lintOnSave": true,
8 |
9 | // 默认使用pylint对Python文件进行静态检查
10 | "python.linting.pylintEnabled": true,
11 |
12 | // 静态检查时是否使用pylint的最小规则集(minimal set of rules)
13 | "python.linting.pylintUseMinimalCheckers": true,
14 |
15 | "python.linting.pylintArgs": [
16 | "--extension-pkg-whitelist=PySide2,cv2"
17 | ],
18 | "python.testing.pytestArgs": [
19 | "."
20 | ],
21 | "python.testing.unittestEnabled": false,
22 | "python.testing.nosetestsEnabled": false,
23 | "python.testing.pytestEnabled": true
24 | }
--------------------------------------------------------------------------------
/ImageTools.iss:
--------------------------------------------------------------------------------
1 | ; Script generated by the Inno Setup Script Wizard.
2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 |
4 | #define MyAppName "ImageTools"
5 | #define MyAppVersion "1.4"
6 | #define MyAppPublisher "liqinxing, Inc."
7 | #define MyAppURL "https://www.qinxing.xyz/"
8 | #define MyAppExeName "ImageTools.exe"
9 |
10 | [Setup]
11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13 | AppId={{12F34F85-9D0D-4F1B-9492-E8875EE23A31}
14 | AppName={#MyAppName}
15 | AppVersion={#MyAppVersion}
16 | ;AppVerName={#MyAppName} {#MyAppVersion}
17 | AppPublisher={#MyAppPublisher}
18 | AppPublisherURL={#MyAppURL}
19 | AppSupportURL={#MyAppURL}
20 | AppUpdatesURL={#MyAppURL}
21 | DefaultDirName=C:\{#MyAppName}
22 | DisableProgramGroupPage=yes
23 | ; Uncomment the following line to run in non administrative install mode (install for current user only.)
24 | ;PrivilegesRequired=lowest
25 | OutputDir=.\Output
26 | OutputBaseFilename=ImageTools
27 | SetupIconFile=.\components\resource\main.ico
28 | Compression=lzma
29 | SolidCompression=yes
30 | WizardStyle=modern
31 |
32 | [Languages]
33 | Name: "english"; MessagesFile: "compiler:Default.isl"
34 |
35 | [Tasks]
36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
37 |
38 | [Files]
39 | Source: ".\dist\ImageTools\ImageTools.exe"; DestDir: "{app}"; Flags: ignoreversion
40 | Source: ".\dist\ImageTools\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
42 |
43 | [Icons]
44 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
45 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
46 |
47 | [Run]
48 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
49 |
50 | [installDelete]
51 | Type: filesandordirs; Name:"{app}\config\*";
52 |
--------------------------------------------------------------------------------
/ImageTools.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QApplication
2 | from PySide2.QtCore import Qt
3 | import sys
4 | from components.window import MainWindow
5 | from components.customwidget import info_win
6 | from tools.depth_of_focus.depth_of_focus import FieldDepthWindow
7 | from tools.shake_test.shake_test import ShakeTestTool
8 | from tools.imageeditor.imageeditor import ImageEditor
9 | from components.help_doc import HelpDoc
10 | from tools.rawimageeditor.RawImageEditor import RawImageEditor
11 | from tools.video_compare.videocompare import VideoCompare
12 | from tools.pqtools_to_code.pqtools_to_code import PQtoolsToCode
13 | from tools.yuv_viewer.yuv_viewer import YUVViewer
14 | from components.check_update import CheckUpdate, simple_check_is_need_update
15 | import components.logconfig as log
16 | from logging import info
17 | from components.property import get_persist, IS_NEED_AUTO_UPDATE
18 |
19 |
20 | class ImageTools(MainWindow):
21 | def __init__(self):
22 | super().__init__()
23 | self.subwindow_function = {
24 | "FieldDepthWindow": [self.ui.field_depth_tool, FieldDepthWindow],
25 | "ShakeTestTool": [self.ui.shake_tool, ShakeTestTool],
26 | "ImageEditor": [self.ui.imageeditor, ImageEditor],
27 | "RawImageEditor": [self.ui.rawimageeditor, RawImageEditor],
28 | "VideoCompare": [self.ui.video_compare, VideoCompare],
29 | "HelpDoc": [self.ui.userguide, HelpDoc],
30 | "PQtoolsToCode": [self.ui.pqtools2code, PQtoolsToCode],
31 | "YUVViewer": [self.ui.yuv_viewer, YUVViewer],
32 | }
33 | self.subwindows_ui = self.ui.mdiArea
34 | self.subwindows_ui.setStyleSheet("QTabBar::tab { height: 30px;}")
35 | self.ui.clearcache.triggered.connect(self.clear_cache)
36 | self.ui.checkupdate.triggered.connect(self.add_checkupdate_window)
37 | for (key, value) in self.subwindow_function.items():
38 | # 注意,这个lambda表达式必须要先赋值才能使用,否则connect的永远是最后的一个类
39 | value[0].triggered.connect(
40 | lambda win_name=key, win_object=value[1]: self.add_sub_window(win_name, win_object))
41 |
42 | info('ImageTools 工具初始化成功')
43 |
44 | def add_sub_window(self, name, win_object):
45 | sub_window = win_object(parent=self)
46 | self.subwindows_ui.addSubWindow(sub_window)
47 | self.sub_windows.append(sub_window)
48 | sub_window.show()
49 | info('打开{}工具 success'.format(name))
50 |
51 | def check_version(self):
52 | is_need_auto_update = get_persist(IS_NEED_AUTO_UPDATE, False)
53 | info('是否需要自动更新 {}'.format(is_need_auto_update))
54 | if is_need_auto_update is True:
55 | if(simple_check_is_need_update() == True):
56 | info('软件需要更新')
57 | self.add_checkupdate_window()
58 | else:
59 | info('软件不需要更新')
60 |
61 | def load_saved_windows(self):
62 | for name in self.sub_windows_list:
63 | self.add_sub_window(name, self.subwindow_function[name][1])
64 |
65 | def add_checkupdate_window(self):
66 | sub_win = CheckUpdate(parent=self)
67 | sub_win.show()
68 |
69 | def clear_cache(self):
70 | self.need_clear_cache = True
71 | info_win('缓存删除成功!\r\n请重启软件', self)
72 |
73 |
74 | if __name__ == "__main__":
75 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
76 | apps = QApplication([])
77 | apps.setStyle('Fusion')
78 | log.clean_old_log()
79 | log.init_log()
80 | appswindow = ImageTools()
81 | appswindow.show()
82 | appswindow.load_saved_windows()
83 | appswindow.check_version()
84 | sys.exit(apps.exec_())
85 |
--------------------------------------------------------------------------------
/buildexe.bat:
--------------------------------------------------------------------------------
1 | DEL /q .\build\*
2 | DEL /q .\dist\*
3 | RD /S /Q .\build
4 | RD /S /Q .\dist
5 | pip install pyinstaller
6 | @REM pyinstaller -w ./ImageTools.py --noconfirm --add-data "./Readme.html;./"
7 | start cmd /c "pyinstaller -w ./ImageTools.py --noconfirm && copy .\Readme.html .\dist\ImageTools\ && copy .\version.md .\dist\ImageTools\ && compil32 /cc ./ImageTools.iss"
8 | @REM start cmd /c "pyinstaller -w --noconfirm ./subapps/PQtools2Code.py && compil32 /cc ./subapps/PQtools2Code.iss"
9 | @REM start cmd /c "pyinstaller -w --noconfirm ./subapps/ShakeTestTool.py && compil32 /cc ./subapps/ShakeTestTool.iss"
10 | @REM start cmd /c "pyinstaller -w --noconfirm ./subapps/FocusDepthTool.py && compil32 /cc ./subapps/FocusDepthTool.iss"
11 | @REM start cmd /c "pyinstaller -w --noconfirm ./subapps/VideoCompare.py && compil32 /cc ./subapps/VideoCompare.iss"
--------------------------------------------------------------------------------
/components/BasicImage.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 | from PySide2.QtGui import QPixmap, QImage
4 | from os import listdir, remove
5 | from os.path import isfile, join, getmtime, dirname, basename, isdir, splitext
6 | from natsort import natsorted
7 | from components.status_code_enum import *
8 |
9 | YUV_FORMAT_MAP = {
10 | 'NV21': cv2.COLOR_YUV2BGR_NV21,
11 | 'NV12': cv2.COLOR_YUV2BGR_NV12,
12 | 'YCrCb': cv2.COLOR_YCrCb2BGR,
13 | 'YUV420': cv2.COLOR_YUV2BGR_I420,
14 | 'YUV422': cv2.COLOR_YUV2BGR_Y422,
15 | 'UYVY': cv2.COLOR_YUV2BGR_UYVY,
16 | 'YUYV': cv2.COLOR_YUV2BGR_YUYV,
17 | 'YVYU': cv2.COLOR_YUV2BGR_YVYU,
18 | }
19 |
20 |
21 | class ImageBasic:
22 | img = None
23 | imgpath = '' # 图片路径
24 | height = 0
25 | width = 0
26 | depth = 0 # 通道数
27 | yuv_format = ''
28 |
29 | def __update_attr(self):
30 | if (self.img is not None):
31 | self.height = self.img.shape[0]
32 | self.width = self.img.shape[1]
33 | self.depth = self.img.shape[2]
34 |
35 | def get_dir(self):
36 | if self.imgpath == '':
37 | return '.'
38 | return dirname(self.imgpath)
39 |
40 | def remove_image(self):
41 | self.img = None
42 | if isfile(self.imgpath) is False:
43 | return
44 | remove(self.imgpath)
45 |
46 | def load_yuv_config(self, width, height, yuv_format):
47 | self.width = width
48 | self.height = height
49 | self.yuv_format = yuv_format
50 |
51 | def load_file(self, filename):
52 | if isfile(filename) is False:
53 | raise FileNotFoundErr
54 | if splitext(filename)[-1] in ['.jpg', '.png', '.bmp']:
55 | self.__load_imagefile(filename)
56 | elif splitext(filename)[-1] in ['.yuv']:
57 | self.__load_yuvfile(filename)
58 | else:
59 | raise ImageFormatNotSupportErr
60 |
61 | def __load_imagefile(self, filename):
62 | # 防止有中文,因此不使用imread
63 | self.img = cv2.imdecode(np.fromfile(filename, dtype=np.uint8), 1)
64 | if self.img is None:
65 | raise ImageReadErr
66 | self.imgpath = filename
67 | self.__update_attr()
68 |
69 | def __load_yuvfile(self, filename):
70 | yuv_format = YUV_FORMAT_MAP.get(self.yuv_format)
71 | if yuv_format is None:
72 | raise ImageFormatNotSupportErr
73 |
74 | yuvdata = np.fromfile(filename, dtype=np.uint8)
75 | if yuvdata is None:
76 | raise ImageNoneErr
77 | if self.yuv_format in ['NV21', 'NV12', 'YUV420']:
78 | if len(yuvdata) != self.height * self.width * 3 / 2:
79 | raise ImageFormatErr
80 | yuvdata = yuvdata.reshape(self.height*3//2, self.width)
81 | elif self.yuv_format in ['YCrCb', 'YUV422', 'UYVY', 'YUYV', 'YVYU']:
82 | if len(yuvdata) != self.height * self.width * 2:
83 | raise ImageFormatErr
84 | yuvdata = yuvdata.reshape(self.height*2, self.width)
85 | else:
86 | raise ImageFormatNotSupportErr
87 |
88 | try:
89 | self.img = cv2.cvtColor(yuvdata, yuv_format)
90 | except Exception:
91 | raise ImageFormatErr
92 | self.imgpath = filename
93 | self.__update_attr()
94 |
95 | # display
96 | def display_in_scene(self, scene):
97 | """
98 | return: true or error string
99 | """
100 | scene.clear()
101 | if self.img is not None:
102 | # numpy转qimage的标准流程
103 | if len(self.img.shape) == 2:
104 | bytes_per_line = self.img.shape[1]
105 | qimg = QImage(
106 | self.img, self.img.shape[1], self.img.shape[0], bytes_per_line, QImage.Format_Grayscale8)
107 | elif self.img.shape[2] == 3:
108 | bytes_per_line = 3 * self.img.shape[1]
109 | qimg = QImage(
110 | self.img, self.img.shape[1], self.img.shape[0], bytes_per_line, QImage.Format_BGR888)
111 | elif self.img.shape[2] == 4:
112 | bytes_per_line = 4 * self.img.shape[1]
113 | qimg = QImage(
114 | self.img, self.img.shape[1], self.img.shape[0], bytes_per_line, QImage.Format_RGBA8888)
115 | else:
116 | raise ImageFormatNotSupportErr
117 | scene.addPixmap(QPixmap.fromImage(qimg))
118 | return
119 | raise ImageNoneErr
120 |
121 | # proc
122 | def save_image(self, filename):
123 | if self.img is None:
124 | raise ImageNoneErr
125 | if isdir(dirname(filename)) is False:
126 | raise FilePathNotValidErr
127 | # 解决中文路径的问题, 不使用imwrite
128 | cv2.imencode('.jpg', self.img)[1].tofile(filename)
129 |
130 | def get_img_point(self, x, y):
131 | """
132 | 获取图像中一个点的RGB值,注意颜色顺序是BGR
133 | """
134 | if(x > 0 and x < self.width and y > 0 and y < self.height):
135 | return self.img[y, x]
136 | else:
137 | return None
138 |
139 | def rotate90(self):
140 | """
141 | 顺时针旋转90度
142 | """
143 | if self.img is None:
144 | raise ImageNoneErr
145 | self.img = cv2.rotate(self.img, cv2.ROTATE_90_CLOCKWISE)
146 | self.__update_attr()
147 |
148 | def find_next_time_photo(self, nextIndex):
149 | """
150 | 获取下一个或者上一个图片(按照时间顺序排列)
151 | """
152 | next_photo_name = ''
153 | index = 0
154 | path = dirname(self.imgpath)
155 | img_name = basename(self.imgpath)
156 | filelist = [f for f in listdir(path) if isfile(
157 | join(path, f)) and f.split('.')[-1] in ["jpg", "png", "bmp"]]
158 | filelist = sorted(
159 | filelist, key=lambda x: getmtime(join(path, x)))
160 | files_nums = len(filelist)
161 | if img_name in filelist:
162 | index = filelist.index(img_name) + nextIndex
163 | if(index > len(filelist) - 1):
164 | index = 0
165 | elif(index < 0):
166 | index = len(filelist) - 1
167 | next_photo_name = join(path, filelist[index])
168 | return (next_photo_name, index, files_nums)
169 |
170 | def find_next_nat_photo(self, nextIndex):
171 | """
172 | 获取下一个或者上一个图片(按照自然顺序排列)
173 | """
174 | next_photo_name = ''
175 | index = 0
176 | path = dirname(self.imgpath)
177 | img_name = basename(self.imgpath)
178 | filelist = [f for f in listdir(path) if isfile(
179 | join(path, f)) and f.split('.')[-1] in ["jpg", "png", "bmp"]]
180 | natsorted(filelist)
181 | files_nums = len(filelist)
182 | if img_name in filelist:
183 | index = filelist.index(img_name) + nextIndex
184 | if(index > len(filelist) - 1):
185 | index = 0
186 | elif(index < 0):
187 | index = len(filelist) - 1
188 | next_photo_name = join(path, filelist[index])
189 | return (next_photo_name, index, files_nums)
190 |
--------------------------------------------------------------------------------
/components/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/__init__.py
--------------------------------------------------------------------------------
/components/check_update.py:
--------------------------------------------------------------------------------
1 | from os import startfile, mkdir
2 | from os.path import exists, abspath, dirname
3 | from sys import exit
4 | from PySide2.QtWidgets import QDialog
5 | from components.ui.check_update_win import Ui_CheckUpdate
6 | import requests
7 | import time
8 | from logging import error, info
9 | from PySide2.QtCore import Signal, QThread
10 | from components.customwidget import critical_win
11 | from components.property import set_persist, get_persist, IS_NEED_AUTO_UPDATE
12 |
13 | VERSION_NUMS_LEN = 3
14 | VERSION_FILE = 'https://imagetools.qinxing.xyz/version.md'
15 |
16 | CURRENT_VERSION_FILE = 'version.md'
17 | REMOTE_INSTALL_PACK = 'https://imagetools.qinxing.xyz/ImageTools.exe'
18 | DOWNLOAD_INSTALL_PACK = '.\\latest_installer\\installer.exe'
19 |
20 |
21 | def check_is_latest(current, new) -> bool:
22 | """
23 | func: 检查是否为最新版本
24 | """
25 | current_version = current.split('.')
26 | new_version = new.split('.')
27 | for i in range(VERSION_NUMS_LEN):
28 | if(int(current_version[i]) < int(new_version[i])):
29 | return False
30 | if(int(current_version[i]) > int(new_version[i])):
31 | return True
32 | return True
33 |
34 |
35 | def get_latest_version_log():
36 | """
37 | func: 获取最新的版本日志文件
38 | 把日志转换成UTF-8格式
39 | """
40 | try:
41 | res = requests.get(VERSION_FILE, timeout=2)
42 | if res.status_code != 200:
43 | return ''
44 | res.encoding = 'utf-8'
45 | res.close()
46 | return res.text
47 | except Exception as e:
48 | error(e)
49 | return ''
50 |
51 |
52 | def get_current_version_log():
53 | """
54 | func: 获取当前版本日志
55 | """
56 | with open(CURRENT_VERSION_FILE, 'r', encoding='utf-8') as f:
57 | return f.read()
58 |
59 |
60 | def get_version(version_log):
61 | """
62 | func: 获取日志文件的最新版本号
63 | 日志文件格式必须为 **x.x.x**
64 | """
65 | start_index = version_log.find('**')
66 | end_index = version_log.find('**', start_index + 1)
67 | return version_log[start_index + 2:end_index]
68 |
69 |
70 | def download_file(srcUrl):
71 | """
72 | func: 下载一个大文件
73 | """
74 | localFile = srcUrl.split('/')[-1]
75 | info('开始下载 %s' % (srcUrl))
76 | startTime = time.time()
77 | with requests.get(srcUrl, stream=True) as r:
78 | contentLength = int(r.headers['content-length'])
79 | line = 'content-length: %dB/ %.2fKB/ %.2fMB'
80 | line = line % (contentLength, contentLength /
81 | 1024, contentLength/1024/1024)
82 | info(line)
83 | downSize = 0
84 | with open(localFile, 'wb') as f:
85 | for chunk in r.iter_content(8192):
86 | if chunk:
87 | f.write(chunk)
88 | downSize += len(chunk)
89 | # line = '%d KB/s - %.2f MB, 共 %.2f MB'
90 | # line = line % (downSize/1024/(time.time()-startTime),
91 | # downSize/1024/1024, contentLength/1024/1024)
92 | # info(line)
93 | if downSize >= contentLength:
94 | break
95 | timeCost = time.time() - startTime
96 | info('下载成功, 共耗时: %.2f s, 平均速度: %.2f KB/s' %
97 | (timeCost, downSize/1024/timeCost))
98 | return True
99 |
100 |
101 | def simple_check_is_need_update() -> bool:
102 | """
103 | func: 简单的返回是否需要更新
104 | """
105 | current_version = get_version(get_current_version_log())
106 | version_log = get_latest_version_log()
107 | if(version_log == ''):
108 | return False
109 | latest_version = get_version(version_log)
110 | if check_is_latest(current_version, latest_version) is True:
111 | return False
112 | return True
113 |
114 |
115 | def check_update():
116 | """
117 | 检查更新
118 | """
119 | current_log = get_current_version_log()
120 | current_version = get_version(current_log)
121 |
122 | version_log = get_latest_version_log()
123 | if(version_log == ''):
124 | return 'connect error', current_version, current_log
125 |
126 | latest_version = get_version(version_log)
127 | if check_is_latest(current_version, latest_version) is True:
128 | return 'already latest', current_version, current_log
129 | else:
130 | return latest_version, current_version, version_log
131 |
132 |
133 | class CheckUpdate(QDialog):
134 |
135 | def __init__(self, parent):
136 | """
137 | func: 初始化直方图统计信息UI,把父类的指针变成选中方框模式
138 | """
139 | super().__init__(parent)
140 | self.ui = Ui_CheckUpdate()
141 | self.ui.setupUi(self)
142 | self.autoupdate = get_persist(IS_NEED_AUTO_UPDATE, False)
143 | self.ui.autoupdate.setChecked(self.autoupdate)
144 | self.need_update = 0
145 | self.ui.autoupdate.stateChanged.connect(self.set_autoupdate_param)
146 | self.ui.ok.clicked.connect(self.start_update)
147 | self.ui.cancel.clicked.connect(lambda: self.close())
148 | self.update_proc = UpdateProc(
149 | REMOTE_INSTALL_PACK, self)
150 | self.update_proc.doneCB.connect(self.update_proc_done)
151 | self.update_proc.processRateCB.connect(
152 | lambda rate: self.ui.progress.setValue(rate))
153 |
154 | latest_version, current_version, latest_log = check_update()
155 |
156 | # 异常处理
157 | if(latest_version == 'connect error'):
158 | self.ui.version_num.setText(
159 | '!!!网络连接出错!!! 当前版本为 ' + current_version)
160 | self.need_update = -1
161 | elif(latest_version == 'already latest'):
162 | self.ui.version_num.setText('软件已经是最新啦!当前版本为 ' + current_version)
163 | self.need_update = 0
164 | else:
165 | self.ui.version_num.setText(
166 | '有版本更新啦!当前版本为 ' + current_version + '; 最新版本为 ' + latest_version)
167 | self.need_update = 1
168 |
169 | self.ui.textBrowser.setMarkdown(latest_log)
170 |
171 | def set_autoupdate_param(self, p):
172 | self.autoupdate = (p != 0)
173 | set_persist(IS_NEED_AUTO_UPDATE, self.autoupdate)
174 | info('设置自动更新功能 {}'.format(self.autoupdate))
175 |
176 | def start_update(self):
177 | if self.need_update == 1:
178 | if(not exists(dirname(DOWNLOAD_INSTALL_PACK))):
179 | mkdir(dirname(DOWNLOAD_INSTALL_PACK))
180 | self.update_proc.start()
181 | elif self.need_update == 0:
182 | critical_win('软件已经是最新啦')
183 | elif self.need_update == -1:
184 | critical_win('网络连接出错')
185 |
186 | def update_proc_done(self, ret):
187 | if(ret == 0):
188 | info('下载成功')
189 | # Popen('start ' + REMOTE_INSTALL_PACK.split('/')
190 | # [-1], shell=True, stdin=PIPE, stdout=PIPE)
191 | # os.system('start ChromeSetup.exe')
192 | if exists(DOWNLOAD_INSTALL_PACK) is True:
193 | startfile(DOWNLOAD_INSTALL_PACK)
194 | info('正在启用最新升级包')
195 | exit()
196 | critical_win('下载的升级包不存在')
197 | else:
198 | info('下载失败')
199 |
200 |
201 | class UpdateProc(QThread):
202 | doneCB = Signal(int)
203 | processRateCB = Signal(int)
204 |
205 | def __init__(self, url, parent=None) -> None:
206 | super(UpdateProc, self).__init__(parent=parent)
207 | self.url = url
208 |
209 | def run(self):
210 | self.processRateCB.emit(0)
211 | info('开始下载更新...')
212 | with requests.get(self.url, stream=True, timeout=5) as r:
213 | contentLength = int(r.headers['content-length'])
214 | downSize = 0
215 | with open(DOWNLOAD_INSTALL_PACK, 'wb') as f:
216 | for chunk in r.iter_content(8192):
217 | if chunk:
218 | f.write(chunk)
219 | downSize += len(chunk)
220 | self.processRateCB.emit(downSize/contentLength * 100)
221 | info('下载进度{}%'.format(downSize/contentLength * 100))
222 | if downSize >= contentLength:
223 | self.doneCB.emit(0)
224 | return
225 | self.doneCB.emit(-1)
226 | return
227 |
--------------------------------------------------------------------------------
/components/help_doc.py:
--------------------------------------------------------------------------------
1 | from components.ui.help_window import Ui_HelpWindow
2 | from components.window import SubWindow
3 |
4 |
5 | class HelpDoc(SubWindow):
6 |
7 | def __init__(self, name='HelpDoc', parent=None):
8 | super().__init__(name, parent, Ui_HelpWindow())
9 | self.ui.textBrowser.setSource("Readme.html")
--------------------------------------------------------------------------------
/components/histview.py:
--------------------------------------------------------------------------------
1 | from components.ui.histgramview import Ui_HistgramView
2 | from PySide2.QtWidgets import QGraphicsView, QDialog
3 | from components.customwidget import MatplotlibWidget
4 | import numpy as np
5 | import cv2
6 |
7 |
8 | class HistView(QDialog):
9 | def __init__(self, parent):
10 | """
11 | func: 初始化直方图统计信息UI,把父类的指针变成选中方框模式
12 | """
13 | super().__init__(parent)
14 | self.parent = parent
15 | self.parent.setDragMode(QGraphicsView.RubberBandDrag)
16 | self.ui = Ui_HistgramView()
17 | self.ui.setupUi(self)
18 | self.ui.r_enable.stateChanged.connect(
19 | self.on_r_hist_enable)
20 | self.ui.g_enable.stateChanged.connect(
21 | self.on_g_hist_enable)
22 | self.ui.b_enable.stateChanged.connect(
23 | self.on_b_hist_enable)
24 | self.ui.y_enable.stateChanged.connect(
25 | self.on_y_hist_enable)
26 | self.histview = MatplotlibWidget(
27 | self.ui.gridLayout_10)
28 | self.x_axis = np.linspace(0, 255, num=256)
29 | self.r_hist_visible = 2
30 | self.g_hist_visible = 2
31 | self.b_hist_visible = 2
32 | self.y_hist_visible = 2
33 | self.enable = True
34 |
35 | def update_rect_data(self, img, rect):
36 | """
37 | func: 更新方框内的图像统计信息
38 | """
39 | (rect, image) = self.update_rect(img, rect)
40 | self.calcHist(image)
41 | self.hist_show()
42 | self.stats_show(self.calcStatics(image, rect))
43 |
44 | def closeEvent(self, event):
45 | """
46 | func: 关闭窗口的时候,把鼠标还原
47 | """
48 | self.parent.setDragMode(QGraphicsView.ScrollHandDrag)
49 | self.enable = False
50 | return super().closeEvent(event)
51 |
52 | def update_rect(self, img, rect):
53 | [x1, y1, x2, y2] = rect
54 | i1 = max(x1, 0)
55 | i2 = min(x2, img.shape[1])
56 | j1 = max(y1, 0)
57 | j2 = min(img.shape[0], y2)
58 | if (i2 > i1 and j2 > j1):
59 | img = img[j1:j2, i1:i2][:, :, :3]
60 | rect = [j1, i1, j2, i2]
61 | return (rect, img)
62 |
63 | def calcStatics(self, img, rect):
64 | [j1, i1, j2, i2] = rect
65 | (average_rgb, stddv_rgb) = cv2.meanStdDev(img)
66 | # TODO 信噪比的公式有些问题,当标准差为0的时候,信噪比该是无穷大
67 | snr_rgb = 20 * np.log10(average_rgb/stddv_rgb)
68 | img = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
69 | (average_yuv, stddv_yuv) = cv2.meanStdDev(img)
70 | snr_yuv = 20 * np.log10(average_yuv/stddv_yuv)
71 | rgb_ratio = [0.0, 0.0]
72 | awb_gain = [0.0, 0.0, 0.0]
73 | rgb_ratio[0] = average_rgb[2]/average_rgb[1]
74 | rgb_ratio[1] = average_rgb[0]/average_rgb[1]
75 | awb_gain[0] = 1/rgb_ratio[0]
76 | awb_gain[1] = 1
77 | awb_gain[2] = 1/rgb_ratio[1]
78 | enable_rect = [i1, j1, i2-i1, j2-j1]
79 | return (average_rgb, snr_rgb, average_yuv, snr_yuv, rgb_ratio, awb_gain, enable_rect)
80 |
81 | def calcHist(self, img):
82 | chans = cv2.split(img)
83 | self.b_hist = (cv2.calcHist([chans[0]], [0], None, [
84 | 256], [0, 256]))
85 | self.g_hist = (cv2.calcHist([chans[1]], [0], None, [
86 | 256], [0, 256]))
87 | self.r_hist = (cv2.calcHist([chans[2]], [0], None, [
88 | 256], [0, 256]))
89 | # 转为灰度图,然后算亮度直方图
90 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
91 | self.y_hist = (cv2.calcHist([img], [0], None, [
92 | 256], [0, 256]))
93 | self.r_hist.reshape(1, 256)
94 | self.g_hist.reshape(1, 256)
95 | self.b_hist.reshape(1, 256)
96 | self.y_hist.reshape(1, 256)
97 |
98 | def on_r_hist_enable(self, type):
99 | self.r_hist_visible = type
100 | self.hist_show()
101 |
102 | def on_g_hist_enable(self, type):
103 | self.g_hist_visible = type
104 | self.hist_show()
105 |
106 | def on_b_hist_enable(self, type):
107 | self.b_hist_visible = type
108 | self.hist_show()
109 |
110 | def on_y_hist_enable(self, type):
111 | self.y_hist_visible = type
112 | self.hist_show()
113 |
114 | def hist_show(self):
115 | self.histview.clean()
116 | self.histview.label("亮度", "数量")
117 | if (self.r_hist_visible == 2):
118 | self.histview.input_r_hist(self.x_axis, self.r_hist)
119 | if (self.g_hist_visible == 2):
120 | self.histview.input_g_hist(self.x_axis, self.g_hist)
121 | if (self.b_hist_visible == 2):
122 | self.histview.input_b_hist(self.x_axis, self.b_hist)
123 | if (self.y_hist_visible == 2):
124 | self.histview.input_y_hist(self.x_axis, self.y_hist)
125 | self.histview.draw()
126 |
127 | def stats_show(self, value):
128 | (average_rgb, snr_rgb, average_yuv, snr_yuv,
129 | rgb_ratio, awb_gain, enable_rect) = value
130 | self.ui.average_r.setValue(average_rgb[2])
131 | self.ui.average_g.setValue(average_rgb[1])
132 | self.ui.average_b.setValue(average_rgb[0])
133 | self.ui.average_y.setValue(average_yuv[0])
134 | self.ui.average_cr.setValue(average_yuv[1])
135 | self.ui.average_cb.setValue(average_yuv[2])
136 | self.ui.rg_ratio.setValue(rgb_ratio[0])
137 | self.ui.bg_ratio.setValue(rgb_ratio[1])
138 | self.ui.r_gain.setValue(awb_gain[0])
139 | self.ui.g_gain.setValue(awb_gain[1])
140 | self.ui.b_gain.setValue(awb_gain[2])
141 | self.ui.section_x.setValue(enable_rect[0])
142 | self.ui.section_y.setValue(enable_rect[1])
143 | self.ui.section_height.setValue(enable_rect[2])
144 | self.ui.section_width.setValue(enable_rect[3])
145 | self.ui.snr_r.setValue(snr_rgb[2])
146 | self.ui.snr_g.setValue(snr_rgb[1])
147 | self.ui.snr_b.setValue(snr_rgb[0])
148 | self.ui.snr_y.setValue(snr_yuv[0])
149 | self.ui.snr_cr.setValue(snr_yuv[1])
150 | self.ui.snr_cb.setValue(snr_yuv[2])
151 |
--------------------------------------------------------------------------------
/components/logconfig.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import os
3 | import logging
4 |
5 | # 设置日志存放路径
6 | LOGFILE_PATH = '.\\log\\'
7 | if not os.path.exists(LOGFILE_PATH):
8 | os.mkdir(LOGFILE_PATH)
9 |
10 | # 获取今天的日期 格式2019-08-01
11 | TODAY_DATE = str(datetime.date.today())
12 |
13 |
14 | def init_log():
15 | """
16 | func: 初始化日志,只需要在主函数中初始化一次
17 | """
18 | root_logger = logging.getLogger()
19 | root_logger.setLevel(logging.INFO)
20 | handler = logging.FileHandler(
21 | LOGFILE_PATH + TODAY_DATE + '.log', 'a', 'utf-8')
22 | formatter = logging.Formatter(
23 | '%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
24 | handler.setFormatter(formatter)
25 | root_logger.addHandler(handler)
26 | logging.info('logging module init is ok')
27 |
28 |
29 | def clean_old_log():
30 | """
31 | 删除除今天以外的日志
32 | """
33 | # 遍历目录下的所有日志文件 i是文件名
34 | for i in os.listdir(LOGFILE_PATH):
35 | if i != TODAY_DATE + '.log':
36 | os.remove(LOGFILE_PATH + i)
37 |
--------------------------------------------------------------------------------
/components/property.py:
--------------------------------------------------------------------------------
1 | from os.path import exists
2 | import pickle
3 |
4 | CACHE_PERSIST_PATH = './config/property.tmp'
5 |
6 | # 参数表
7 | IS_NEED_AUTO_UPDATE = 0
8 |
9 |
10 | def load_persist():
11 | ret = {}
12 | if exists(CACHE_PERSIST_PATH):
13 | with open(CACHE_PERSIST_PATH, "rb") as fp:
14 | ret = pickle.load(fp)
15 | return ret
16 |
17 |
18 | def dump_persist(value):
19 | with open(CACHE_PERSIST_PATH, "wb") as fp:
20 | pickle.dump(value, fp)
21 |
22 |
23 | def get_persist(key, default_value):
24 | p = load_persist()
25 | if key in p:
26 | return p[key]
27 | else:
28 | return default_value
29 |
30 |
31 | def set_persist(key, value):
32 | p = load_persist()
33 | p[key] = value
34 | dump_persist(p)
35 |
--------------------------------------------------------------------------------
/components/resource.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | resource/add.png
4 | resource/subtract.png
5 | resource/快进.png
6 | resource/快退.png
7 | resource/restart .png
8 | resource/save_icon.png
9 | resource/compare.png
10 | resource/start.png
11 | resource/pause.png
12 | resource/stop.png
13 | resource/main.png
14 | resource/open.png
15 | resource/stats.png
16 | resource/down.png
17 | resource/up.png
18 | resource/delete.png
19 | resource/right.png
20 | resource/config.png
21 |
22 |
23 |
--------------------------------------------------------------------------------
/components/resource/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/add.png
--------------------------------------------------------------------------------
/components/resource/compare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/compare.png
--------------------------------------------------------------------------------
/components/resource/config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/config.png
--------------------------------------------------------------------------------
/components/resource/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/delete.png
--------------------------------------------------------------------------------
/components/resource/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/down.png
--------------------------------------------------------------------------------
/components/resource/main.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/main.ico
--------------------------------------------------------------------------------
/components/resource/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/main.png
--------------------------------------------------------------------------------
/components/resource/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/open.png
--------------------------------------------------------------------------------
/components/resource/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/pause.png
--------------------------------------------------------------------------------
/components/resource/restart .png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/restart .png
--------------------------------------------------------------------------------
/components/resource/right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/right.png
--------------------------------------------------------------------------------
/components/resource/save_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/save_icon.png
--------------------------------------------------------------------------------
/components/resource/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/start.png
--------------------------------------------------------------------------------
/components/resource/stats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/stats.png
--------------------------------------------------------------------------------
/components/resource/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/stop.png
--------------------------------------------------------------------------------
/components/resource/subtract.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/subtract.png
--------------------------------------------------------------------------------
/components/resource/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/up.png
--------------------------------------------------------------------------------
/components/resource/快进.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/快进.png
--------------------------------------------------------------------------------
/components/resource/快退.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/components/resource/快退.png
--------------------------------------------------------------------------------
/components/status_code_enum.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from logging import error
3 | from traceback import format_exc
4 | from components.customwidget import critical_win
5 |
6 |
7 | class StatusCode(Enum):
8 | """状态码枚举类"""
9 | OK = '成功'
10 | ERROR = '错误'
11 | FILE_NOT_FOUND = '文件不存在'
12 | FILE_PATH_NOT_VALID = '文件路径不合法'
13 | IMAGE_FORMAT_ERR = '图像格式错误'
14 | IMAGE_FORMAT_NOT_SUPPORT = '图像格式不支持'
15 | IMAGE_READ_ERR = '图像读取失败'
16 | IMAGE_IS_NONE = '图片为空'
17 |
18 |
19 | class ImageToolError(Exception):
20 | def show(self):
21 | error(format_exc())
22 | critical_win(str(self))
23 |
24 |
25 | class FileNotFoundErr(ImageToolError):
26 | def __init__(self):
27 | super().__init__('文件不存在')
28 |
29 |
30 | class FilePathNotValidErr(ImageToolError):
31 | def __init__(self):
32 | super().__init__('文件路径不合法')
33 |
34 |
35 | class ImageFormatErr(ImageToolError):
36 | def __init__(self):
37 | super().__init__('图像格式错误')
38 |
39 |
40 | class ImageFormatNotSupportErr(ImageToolError):
41 | def __init__(self):
42 | super().__init__('图像格式不支持')
43 |
44 |
45 | class ImageReadErr(ImageToolError):
46 | def __init__(self):
47 | super().__init__('图像读取失败')
48 |
49 |
50 | class ImageNoneErr(ImageToolError):
51 | def __init__(self):
52 | super().__init__('图片为空')
53 |
--------------------------------------------------------------------------------
/components/ui/check_update_win.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'check_update_win.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import *
12 | from PySide2.QtGui import *
13 | from PySide2.QtWidgets import *
14 |
15 |
16 | class Ui_CheckUpdate(object):
17 | def setupUi(self, CheckUpdate):
18 | if not CheckUpdate.objectName():
19 | CheckUpdate.setObjectName(u"CheckUpdate")
20 | CheckUpdate.resize(395, 294)
21 | self.gridLayout = QGridLayout(CheckUpdate)
22 | self.gridLayout.setObjectName(u"gridLayout")
23 | self.verticalLayout = QVBoxLayout()
24 | self.verticalLayout.setObjectName(u"verticalLayout")
25 | self.version_num = QLabel(CheckUpdate)
26 | self.version_num.setObjectName(u"version_num")
27 |
28 | self.verticalLayout.addWidget(self.version_num)
29 |
30 | self.textBrowser = QTextBrowser(CheckUpdate)
31 | self.textBrowser.setObjectName(u"textBrowser")
32 | self.textBrowser.setOpenExternalLinks(True)
33 |
34 | self.verticalLayout.addWidget(self.textBrowser)
35 |
36 | self.horizontalLayout = QHBoxLayout()
37 | self.horizontalLayout.setObjectName(u"horizontalLayout")
38 | self.autoupdate = QCheckBox(CheckUpdate)
39 | self.autoupdate.setObjectName(u"autoupdate")
40 |
41 | self.horizontalLayout.addWidget(self.autoupdate)
42 |
43 | self.progress = QProgressBar(CheckUpdate)
44 | self.progress.setObjectName(u"progress")
45 | self.progress.setValue(0)
46 |
47 | self.horizontalLayout.addWidget(self.progress)
48 |
49 | self.ok = QPushButton(CheckUpdate)
50 | self.ok.setObjectName(u"ok")
51 | self.ok.setLayoutDirection(Qt.RightToLeft)
52 |
53 | self.horizontalLayout.addWidget(self.ok)
54 |
55 | self.cancel = QPushButton(CheckUpdate)
56 | self.cancel.setObjectName(u"cancel")
57 | self.cancel.setLayoutDirection(Qt.RightToLeft)
58 |
59 | self.horizontalLayout.addWidget(self.cancel)
60 |
61 |
62 | self.verticalLayout.addLayout(self.horizontalLayout)
63 |
64 |
65 | self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
66 |
67 |
68 | self.retranslateUi(CheckUpdate)
69 |
70 | QMetaObject.connectSlotsByName(CheckUpdate)
71 | # setupUi
72 |
73 | def retranslateUi(self, CheckUpdate):
74 | CheckUpdate.setWindowTitle(QCoreApplication.translate("CheckUpdate", u"\u68c0\u67e5\u66f4\u65b0", None))
75 | self.version_num.setText("")
76 | self.autoupdate.setText(QCoreApplication.translate("CheckUpdate", u"\u81ea\u52a8\u66f4\u65b0", None))
77 | self.ok.setText(QCoreApplication.translate("CheckUpdate", u"\u7acb\u523b\u66f4\u65b0", None))
78 | self.cancel.setText(QCoreApplication.translate("CheckUpdate", u"\u4ee5\u540e\u518d\u8bf4", None))
79 | # retranslateUi
80 |
81 |
--------------------------------------------------------------------------------
/components/ui/check_update_win.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | CheckUpdate
4 |
5 |
6 |
7 | 0
8 | 0
9 | 395
10 | 294
11 |
12 |
13 |
14 | 检查更新
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 |
23 |
24 |
25 |
26 | -
27 |
28 |
29 | true
30 |
31 |
32 |
33 | -
34 |
35 |
-
36 |
37 |
38 | 自动更新
39 |
40 |
41 |
42 | -
43 |
44 |
45 | 0
46 |
47 |
48 |
49 | -
50 |
51 |
52 | Qt::RightToLeft
53 |
54 |
55 | 立刻更新
56 |
57 |
58 |
59 | -
60 |
61 |
62 | Qt::RightToLeft
63 |
64 |
65 | 以后再说
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/components/ui/help_window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'help_window.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import *
12 | from PySide2.QtGui import *
13 | from PySide2.QtWidgets import *
14 |
15 |
16 | class Ui_HelpWindow(object):
17 | def setupUi(self, HelpWindow):
18 | if not HelpWindow.objectName():
19 | HelpWindow.setObjectName(u"HelpWindow")
20 | HelpWindow.resize(800, 600)
21 | self.centralwidget = QWidget(HelpWindow)
22 | self.centralwidget.setObjectName(u"centralwidget")
23 | self.gridLayout = QGridLayout(self.centralwidget)
24 | self.gridLayout.setObjectName(u"gridLayout")
25 | self.textBrowser = QTextBrowser(self.centralwidget)
26 | self.textBrowser.setObjectName(u"textBrowser")
27 | font = QFont()
28 | font.setFamily(u"\u5fae\u8f6f\u96c5\u9ed1")
29 | font.setPointSize(15)
30 | self.textBrowser.setFont(font)
31 | self.textBrowser.setAcceptRichText(False)
32 | self.textBrowser.setOpenExternalLinks(True)
33 | self.textBrowser.setOpenLinks(True)
34 |
35 | self.gridLayout.addWidget(self.textBrowser, 0, 0, 1, 1)
36 |
37 | HelpWindow.setCentralWidget(self.centralwidget)
38 | self.menubar = QMenuBar(HelpWindow)
39 | self.menubar.setObjectName(u"menubar")
40 | self.menubar.setGeometry(QRect(0, 0, 800, 22))
41 | HelpWindow.setMenuBar(self.menubar)
42 | self.statusbar = QStatusBar(HelpWindow)
43 | self.statusbar.setObjectName(u"statusbar")
44 | HelpWindow.setStatusBar(self.statusbar)
45 |
46 | self.retranslateUi(HelpWindow)
47 |
48 | QMetaObject.connectSlotsByName(HelpWindow)
49 | # setupUi
50 |
51 | def retranslateUi(self, HelpWindow):
52 | HelpWindow.setWindowTitle(QCoreApplication.translate("HelpWindow", u"\u5e2e\u52a9\u624b\u518c", None))
53 | # retranslateUi
54 |
55 |
--------------------------------------------------------------------------------
/components/ui/help_window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | HelpWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 800
10 | 600
11 |
12 |
13 |
14 | 帮助手册
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
22 | 微软雅黑
23 | 15
24 |
25 |
26 |
27 | false
28 |
29 |
30 | true
31 |
32 |
33 | true
34 |
35 |
36 |
37 |
38 |
39 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/components/ui/mainwindow.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'mainwindow.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import *
12 | from PySide2.QtGui import *
13 | from PySide2.QtWidgets import *
14 |
15 |
16 | class Ui_MainWindow(object):
17 | def setupUi(self, MainWindow):
18 | if not MainWindow.objectName():
19 | MainWindow.setObjectName(u"MainWindow")
20 | MainWindow.setWindowModality(Qt.NonModal)
21 | MainWindow.resize(1140, 857)
22 | icon = QIcon()
23 | icon.addFile(u":/tool_icon/resource/main.png", QSize(), QIcon.Normal, QIcon.Off)
24 | MainWindow.setWindowIcon(icon)
25 | self.shake_tool = QAction(MainWindow)
26 | self.shake_tool.setObjectName(u"shake_tool")
27 | self.imageeditor = QAction(MainWindow)
28 | self.imageeditor.setObjectName(u"imageeditor")
29 | self.about = QAction(MainWindow)
30 | self.about.setObjectName(u"about")
31 | self.rawimageeditor = QAction(MainWindow)
32 | self.rawimageeditor.setObjectName(u"rawimageeditor")
33 | self.video_compare = QAction(MainWindow)
34 | self.video_compare.setObjectName(u"video_compare")
35 | self.pqtools2code = QAction(MainWindow)
36 | self.pqtools2code.setObjectName(u"pqtools2code")
37 | self.field_depth_tool = QAction(MainWindow)
38 | self.field_depth_tool.setObjectName(u"field_depth_tool")
39 | self.af_calc_tool = QAction(MainWindow)
40 | self.af_calc_tool.setObjectName(u"af_calc_tool")
41 | self.userguide = QAction(MainWindow)
42 | self.userguide.setObjectName(u"userguide")
43 | self.clearcache = QAction(MainWindow)
44 | self.clearcache.setObjectName(u"clearcache")
45 | self.checkupdate = QAction(MainWindow)
46 | self.checkupdate.setObjectName(u"checkupdate")
47 | self.yuv_viewer = QAction(MainWindow)
48 | self.yuv_viewer.setObjectName(u"yuv_viewer")
49 | self.centralwidget = QWidget(MainWindow)
50 | self.centralwidget.setObjectName(u"centralwidget")
51 | self.gridLayout_2 = QGridLayout(self.centralwidget)
52 | self.gridLayout_2.setObjectName(u"gridLayout_2")
53 | self.mdiArea = QMdiArea(self.centralwidget)
54 | self.mdiArea.setObjectName(u"mdiArea")
55 | self.mdiArea.setAcceptDrops(True)
56 | self.mdiArea.setLineWidth(0)
57 | self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
58 | self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
59 | self.mdiArea.setViewMode(QMdiArea.TabbedView)
60 | self.mdiArea.setTabsClosable(True)
61 | self.mdiArea.setTabsMovable(True)
62 | self.mdiArea.setTabShape(QTabWidget.Rounded)
63 |
64 | self.gridLayout_2.addWidget(self.mdiArea, 0, 0, 1, 1)
65 |
66 | MainWindow.setCentralWidget(self.centralwidget)
67 | self.menubar = QMenuBar(MainWindow)
68 | self.menubar.setObjectName(u"menubar")
69 | self.menubar.setGeometry(QRect(0, 0, 1140, 22))
70 | self.menu_2 = QMenu(self.menubar)
71 | self.menu_2.setObjectName(u"menu_2")
72 | self.menuISP = QMenu(self.menubar)
73 | self.menuISP.setObjectName(u"menuISP")
74 | self.menu = QMenu(self.menubar)
75 | self.menu.setObjectName(u"menu")
76 | MainWindow.setMenuBar(self.menubar)
77 |
78 | self.menubar.addAction(self.menu_2.menuAction())
79 | self.menubar.addAction(self.menuISP.menuAction())
80 | self.menubar.addAction(self.menu.menuAction())
81 | self.menu_2.addAction(self.shake_tool)
82 | self.menu_2.addAction(self.imageeditor)
83 | self.menu_2.addAction(self.rawimageeditor)
84 | self.menu_2.addAction(self.video_compare)
85 | self.menu_2.addAction(self.yuv_viewer)
86 | self.menuISP.addAction(self.pqtools2code)
87 | self.menuISP.addAction(self.field_depth_tool)
88 | self.menu.addAction(self.userguide)
89 | self.menu.addAction(self.clearcache)
90 | self.menu.addAction(self.checkupdate)
91 |
92 | self.retranslateUi(MainWindow)
93 |
94 | QMetaObject.connectSlotsByName(MainWindow)
95 | # setupUi
96 |
97 | def retranslateUi(self, MainWindow):
98 | MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"ImageTools", None))
99 | self.shake_tool.setText(QCoreApplication.translate("MainWindow", u"\u6296\u52a8\u6d4b\u8bd5\u5de5\u5177", None))
100 | self.imageeditor.setText(QCoreApplication.translate("MainWindow", u"\u56fe\u7247\u5206\u6790\u5de5\u5177", None))
101 | self.about.setText(QCoreApplication.translate("MainWindow", u"\u5173\u4e8e", None))
102 | self.rawimageeditor.setText(QCoreApplication.translate("MainWindow", u"raw\u56fe\u5206\u6790\u5de5\u5177", None))
103 | self.video_compare.setText(QCoreApplication.translate("MainWindow", u"\u89c6\u9891\u5bf9\u6bd4\u5de5\u5177", None))
104 | self.pqtools2code.setText(QCoreApplication.translate("MainWindow", u"PQtools\u8f6c\u4ee3\u7801", None))
105 | self.field_depth_tool.setText(QCoreApplication.translate("MainWindow", u"\u955c\u5934\u8ba1\u7b97\u5668", None))
106 | self.af_calc_tool.setText(QCoreApplication.translate("MainWindow", u"\u955c\u5934\u66f2\u7ebf\u8ba1\u7b97\u5de5\u5177", None))
107 | self.userguide.setText(QCoreApplication.translate("MainWindow", u"\u7528\u6237\u624b\u518c", None))
108 | self.clearcache.setText(QCoreApplication.translate("MainWindow", u"\u6e05\u7406\u7f13\u5b58", None))
109 | self.checkupdate.setText(QCoreApplication.translate("MainWindow", u"\u68c0\u67e5\u66f4\u65b0", None))
110 | self.yuv_viewer.setText(QCoreApplication.translate("MainWindow", u"YUV\u67e5\u770b\u5de5\u5177", None))
111 | self.menu_2.setTitle(QCoreApplication.translate("MainWindow", u"\u5de5\u5177", None))
112 | self.menuISP.setTitle(QCoreApplication.translate("MainWindow", u"ISP", None))
113 | self.menu.setTitle(QCoreApplication.translate("MainWindow", u"\u5173\u4e8e", None))
114 | # retranslateUi
115 |
116 |
--------------------------------------------------------------------------------
/components/ui/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 | Qt::NonModal
7 |
8 |
9 |
10 | 0
11 | 0
12 | 1140
13 | 857
14 |
15 |
16 |
17 | ImageTools
18 |
19 |
20 |
21 | :/tool_icon/resource/main.png:/tool_icon/resource/main.png
22 |
23 |
24 |
25 | -
26 |
27 |
28 | true
29 |
30 |
31 | 0
32 |
33 |
34 | Qt::ScrollBarAsNeeded
35 |
36 |
37 | Qt::ScrollBarAsNeeded
38 |
39 |
40 | QMdiArea::TabbedView
41 |
42 |
43 | true
44 |
45 |
46 | true
47 |
48 |
49 | QTabWidget::Rounded
50 |
51 |
52 |
53 |
54 |
55 |
93 |
94 |
95 | 抖动测试工具
96 |
97 |
98 |
99 |
100 | 图片分析工具
101 |
102 |
103 |
104 |
105 | 关于
106 |
107 |
108 |
109 |
110 | raw图分析工具
111 |
112 |
113 |
114 |
115 | 视频对比工具
116 |
117 |
118 |
119 |
120 | PQtools转代码
121 |
122 |
123 |
124 |
125 | 镜头计算器
126 |
127 |
128 |
129 |
130 | 镜头曲线计算工具
131 |
132 |
133 |
134 |
135 | 用户手册
136 |
137 |
138 |
139 |
140 | 清理缓存
141 |
142 |
143 |
144 |
145 | 检查更新
146 |
147 |
148 |
149 |
150 | YUV查看工具
151 |
152 |
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/components/window.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QMessageBox, QMainWindow, QProgressBar, QLabel
2 | from components.ui.mainwindow import Ui_MainWindow
3 | import pickle
4 | import os
5 |
6 | CACHE_FILEPATH = './config'
7 |
8 |
9 | class MainWindow(QMainWindow):
10 | """对QMainWindow类重写,实现一些功能"""
11 |
12 | def __init__(self):
13 | super().__init__()
14 | self.sub_windows = list()
15 | self.filename = './config/ImageToolsSubWindows.tmp'
16 | self.sub_windows_list = list()
17 | self.ui = Ui_MainWindow()
18 | self.ui.setupUi(self)
19 | self.need_clear_cache = False
20 | if os.path.exists(self.filename):
21 | with open(self.filename, "rb") as fp:
22 | self.sub_windows_list = pickle.load(fp)
23 |
24 | def closeEvent(self, event):
25 | """
26 | 重写closeEvent方法,实现dialog窗体关闭时执行一些代码
27 | :param event: close()触发的事件
28 | :return: None
29 | """
30 | reply = QMessageBox.question(self,
31 | 'ImageTools',
32 | "是否要退出程序?",
33 | QMessageBox.Yes | QMessageBox.No,
34 | QMessageBox.No)
35 | if reply == QMessageBox.Yes:
36 | if self.need_clear_cache == False:
37 | if not os.path.exists(CACHE_FILEPATH):
38 | os.mkdir(CACHE_FILEPATH)
39 | sub_windows_list = list()
40 | while len(self.sub_windows) > 0:
41 | if (self.sub_windows[0].name is not None):
42 | sub_windows_list.append(self.sub_windows[0].name)
43 | self.sub_windows[0].close()
44 | with open(self.filename, "wb") as fp:
45 | pickle.dump(sub_windows_list, fp)
46 | else:
47 | # 清楚缓存
48 | if os.path.exists(CACHE_FILEPATH):
49 | for files in os.listdir(CACHE_FILEPATH):
50 | filepath = os.path.join(CACHE_FILEPATH, files)
51 | if os.path.isfile(filepath):
52 | os.remove(filepath)
53 | event.accept()
54 | else:
55 | event.ignore()
56 |
57 |
58 | class SubWindow(QMainWindow):
59 | """对QMainWindow类重写,实现一些功能"""
60 |
61 | def __init__(self, name, parent, ui_view, need_processBar=False):
62 | super().__init__(parent)
63 | self.parent = parent
64 | self.name = name
65 | self.filename = "./config/" + name + ".tmp"
66 | self.__saved_params = None
67 | self.ui = ui_view
68 | self.ui.setupUi(self)
69 | # add 进度条和详细信息显示 需要在ui里面加入statusBar
70 | if(need_processBar == True):
71 | self.progress_bar = QProgressBar()
72 | self.info_bar = QLabel()
73 | self.time_bar = QLabel()
74 | self.ui.statusBar.addPermanentWidget(self.info_bar, stretch=8)
75 | self.ui.statusBar.addPermanentWidget(self.time_bar, stretch=1)
76 | self.ui.statusBar.addPermanentWidget(self.progress_bar, stretch=2)
77 | self.progress_bar.setRange(0, 100) # 设置进度条的范围
78 | self.progress_bar.setValue(0)
79 |
80 | def load_params(self, init_value):
81 | """
82 | 加载存储的类,返回的参数可以直接进行修改,会保存到本地,下一次打开会自动加载
83 | """
84 | if os.path.exists(self.filename):
85 | with open(self.filename, "rb") as fp:
86 | self.__saved_params = pickle.load(fp)
87 | if (self.__saved_params is None):
88 | self.__saved_params = init_value
89 | return self.__saved_params
90 |
91 | def closeEvent(self, event):
92 | """
93 | 重写closeEvent方法,实现dialog窗体关闭时执行一些代码
94 | :param event: close()触发的事件
95 | :return: None
96 | """
97 | if not os.path.exists(CACHE_FILEPATH):
98 | os.mkdir(CACHE_FILEPATH)
99 | self.name = None
100 | with open(self.filename, "wb") as fp:
101 | pickle.dump(self.__saved_params, fp)
102 | try:
103 | self.parent.sub_windows.remove(self)
104 | except Exception:
105 | print('{}工具不支持记忆存储'.format(self.name))
106 | event.accept()
107 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pyside2
2 | matplotlib
3 | numpy
4 | opencv-python
5 | scipy
6 | numba
7 | PyWavelets
8 | logging
9 | requests
10 | natsort
--------------------------------------------------------------------------------
/subapps/FocusDepthTool.iss:
--------------------------------------------------------------------------------
1 | ; Script generated by the Inno Setup Script Wizard.
2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 |
4 | #define MyAppName "FocusDepthTool"
5 | #define MyAppVersion "1.3"
6 | #define MyAppPublisher "liqinxing, Inc."
7 | #define MyAppURL "https://www.qinxing.xyz/"
8 | #define MyAppExeName "FocusDepthTool.exe"
9 |
10 | [Setup]
11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13 | AppId={{12F34F85-9D0D-4F1B-9492-E8875EE23A31}
14 | AppName={#MyAppName}
15 | AppVersion={#MyAppVersion}
16 | ;AppVerName={#MyAppName} {#MyAppVersion}
17 | AppPublisher={#MyAppPublisher}
18 | AppPublisherURL={#MyAppURL}
19 | AppSupportURL={#MyAppURL}
20 | AppUpdatesURL={#MyAppURL}
21 | DefaultDirName=C:/ImageTools\{#MyAppName}
22 | DisableProgramGroupPage=yes
23 | ; Uncomment the following line to run in non administrative install mode (install for current user only.)
24 | ;PrivilegesRequired=lowest
25 | OutputDir=..\Output
26 | OutputBaseFilename={#MyAppName}
27 | SetupIconFile=..\ui\resource\main.ico
28 | Compression=lzma
29 | SolidCompression=yes
30 | WizardStyle=modern
31 |
32 | [Languages]
33 | Name: "english"; MessagesFile: "compiler:Default.isl"
34 |
35 | [Tasks]
36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
37 |
38 | [Files]
39 | Source: "..\dist\{#MyAppName}\{#MyAppName}.exe"; DestDir: "{app}"; Flags: ignoreversion
40 | Source: "..\dist\{#MyAppName}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
42 |
43 | [Icons]
44 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
45 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
46 |
47 | [Run]
48 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
49 |
50 |
--------------------------------------------------------------------------------
/subapps/FocusDepthTool.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append("..")
3 | from tools.depth_of_focus.depth_of_focus import FieldDepthWindow
4 | from PySide2.QtWidgets import QApplication
5 | from PySide2.QtCore import Qt
6 |
7 | if __name__ == "__main__":
8 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
9 | apps = QApplication([])
10 | apps.setStyle('Fusion')
11 | appswindow = FieldDepthWindow()
12 | appswindow.show()
13 | sys.exit(apps.exec_())
14 |
--------------------------------------------------------------------------------
/subapps/PQtools2Code.iss:
--------------------------------------------------------------------------------
1 | ; Script generated by the Inno Setup Script Wizard.
2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 |
4 | #define MyAppName "PQtools2Code"
5 | #define MyAppVersion "1.3"
6 | #define MyAppPublisher "liqinxing, Inc."
7 | #define MyAppURL "https://www.qinxing.xyz/"
8 | #define MyAppExeName "PQtools2Code.exe"
9 |
10 | [Setup]
11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13 | AppId={{12F34F85-9D0D-4F1B-9492-E8875EE23A31}
14 | AppName={#MyAppName}
15 | AppVersion={#MyAppVersion}
16 | ;AppVerName={#MyAppName} {#MyAppVersion}
17 | AppPublisher={#MyAppPublisher}
18 | AppPublisherURL={#MyAppURL}
19 | AppSupportURL={#MyAppURL}
20 | AppUpdatesURL={#MyAppURL}
21 | DefaultDirName=C:/ImageTools\{#MyAppName}
22 | DisableProgramGroupPage=yes
23 | ; Uncomment the following line to run in non administrative install mode (install for current user only.)
24 | ;PrivilegesRequired=lowest
25 | OutputDir=..\Output
26 | OutputBaseFilename={#MyAppName}
27 | SetupIconFile=..\ui\resource\main.ico
28 | Compression=lzma
29 | SolidCompression=yes
30 | WizardStyle=modern
31 |
32 | [Languages]
33 | Name: "english"; MessagesFile: "compiler:Default.isl"
34 |
35 | [Tasks]
36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
37 |
38 | [Files]
39 | Source: "..\dist\{#MyAppName}\{#MyAppName}.exe"; DestDir: "{app}"; Flags: ignoreversion
40 | Source: "..\dist\{#MyAppName}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
42 |
43 | [Icons]
44 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
45 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
46 |
47 | [Run]
48 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
49 |
50 |
--------------------------------------------------------------------------------
/subapps/PQtools2Code.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append("..")
3 | from tools.pqtools_to_code.pqtools_to_code import PQtoolsToCode
4 | from PySide2.QtWidgets import QApplication
5 | from PySide2.QtCore import Qt
6 |
7 | if __name__ == "__main__":
8 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
9 | apps = QApplication([])
10 | apps.setStyle('Fusion')
11 | appswindow = PQtoolsToCode()
12 | appswindow.show()
13 | sys.exit(apps.exec_())
14 |
--------------------------------------------------------------------------------
/subapps/ShakeTestTool.iss:
--------------------------------------------------------------------------------
1 | ; Script generated by the Inno Setup Script Wizard.
2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 |
4 | #define MyAppName "ShakeTestTool"
5 | #define MyAppVersion "1.3"
6 | #define MyAppPublisher "liqinxing, Inc."
7 | #define MyAppURL "https://www.qinxing.xyz/"
8 | #define MyAppExeName "ShakeTestTool.exe"
9 |
10 | [Setup]
11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13 | AppId={{12F34F85-9D0D-4F1B-9492-E8875EE23A31}
14 | AppName={#MyAppName}
15 | AppVersion={#MyAppVersion}
16 | ;AppVerName={#MyAppName} {#MyAppVersion}
17 | AppPublisher={#MyAppPublisher}
18 | AppPublisherURL={#MyAppURL}
19 | AppSupportURL={#MyAppURL}
20 | AppUpdatesURL={#MyAppURL}
21 | DefaultDirName=C:/ImageTools\{#MyAppName}
22 | DisableProgramGroupPage=yes
23 | ; Uncomment the following line to run in non administrative install mode (install for current user only.)
24 | ;PrivilegesRequired=lowest
25 | OutputDir=..\Output
26 | OutputBaseFilename={#MyAppName}
27 | SetupIconFile=..\ui\resource\main.ico
28 | Compression=lzma
29 | SolidCompression=yes
30 | WizardStyle=modern
31 |
32 | [Languages]
33 | Name: "english"; MessagesFile: "compiler:Default.isl"
34 |
35 | [Tasks]
36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
37 |
38 | [Files]
39 | Source: "..\dist\{#MyAppName}\{#MyAppName}.exe"; DestDir: "{app}"; Flags: ignoreversion
40 | Source: "..\dist\{#MyAppName}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
42 |
43 | [Icons]
44 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
45 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
46 |
47 | [Run]
48 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
49 |
50 |
--------------------------------------------------------------------------------
/subapps/ShakeTestTool.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append("..")
3 | from tools.shake_test.shake_test import ShakeTestTool
4 | from PySide2.QtWidgets import QApplication
5 | from PySide2.QtCore import Qt
6 |
7 | if __name__ == "__main__":
8 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
9 | apps = QApplication([])
10 | apps.setStyle('Fusion')
11 | appswindow = ShakeTestTool()
12 | appswindow.show()
13 | sys.exit(apps.exec_())
14 |
--------------------------------------------------------------------------------
/subapps/VideoCompare.iss:
--------------------------------------------------------------------------------
1 | ; Script generated by the Inno Setup Script Wizard.
2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3 |
4 | #define MyAppName "VideoCompare"
5 | #define MyAppVersion "1.3"
6 | #define MyAppPublisher "liqinxing, Inc."
7 | #define MyAppURL "https://www.qinxing.xyz/"
8 | #define MyAppExeName "VideoCompare.exe"
9 |
10 | [Setup]
11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13 | AppId={{12F34F85-9D0D-4F1B-9492-E8875EE23A31}
14 | AppName={#MyAppName}
15 | AppVersion={#MyAppVersion}
16 | ;AppVerName={#MyAppName} {#MyAppVersion}
17 | AppPublisher={#MyAppPublisher}
18 | AppPublisherURL={#MyAppURL}
19 | AppSupportURL={#MyAppURL}
20 | AppUpdatesURL={#MyAppURL}
21 | DefaultDirName=C:/ImageTools\{#MyAppName}
22 | DisableProgramGroupPage=yes
23 | ; Uncomment the following line to run in non administrative install mode (install for current user only.)
24 | ;PrivilegesRequired=lowest
25 | OutputDir=..\Output
26 | OutputBaseFilename={#MyAppName}
27 | SetupIconFile=..\ui\resource\main.ico
28 | Compression=lzma
29 | SolidCompression=yes
30 | WizardStyle=modern
31 |
32 | [Languages]
33 | Name: "english"; MessagesFile: "compiler:Default.isl"
34 |
35 | [Tasks]
36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
37 |
38 | [Files]
39 | Source: "..\dist\{#MyAppName}\{#MyAppName}.exe"; DestDir: "{app}"; Flags: ignoreversion
40 | Source: "..\dist\{#MyAppName}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
42 |
43 | [Icons]
44 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
45 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
46 |
47 | [Run]
48 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
49 |
50 |
--------------------------------------------------------------------------------
/subapps/VideoCompare.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append("..")
3 | from tools.video_compare.videocompare import VideoCompare
4 | from PySide2.QtWidgets import QApplication
5 | from PySide2.QtCore import Qt
6 |
7 | if __name__ == "__main__":
8 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
9 | apps = QApplication([])
10 | apps.setStyle('Fusion')
11 | appswindow = VideoCompare()
12 | appswindow.show()
13 | sys.exit(apps.exec_())
14 |
--------------------------------------------------------------------------------
/subapps/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/subapps/__init__.py
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/__init__.py
--------------------------------------------------------------------------------
/test/components/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/components/__init__.py
--------------------------------------------------------------------------------
/test/components/test_basic_image.py:
--------------------------------------------------------------------------------
1 | from os.path import samefile, isfile
2 | from components.BasicImage import ImageBasic
3 | from components.status_code_enum import StatusCode
4 | from components.status_code_enum import *
5 |
6 |
7 | def test_status_code():
8 | ret = StatusCode.OK
9 | if ret is StatusCode.OK:
10 | assert True
11 |
12 | ret = StatusCode.FILE_NOT_FOUND
13 | if ret is StatusCode.OK:
14 | assert '文件不存在' == StatusCode.FILE_NOT_FOUND.value
15 | assert StatusCode.FILE_NOT_FOUND.name == 'FILE_NOT_FOUND'
16 |
17 |
18 | class TestBasicImage:
19 | def test_load_imagefile(self):
20 | img = ImageBasic()
21 | try:
22 | img.load_file("test/resource/5.jpg")
23 | assert False
24 | except FileNotFoundErr:
25 | assert True
26 | except Exception:
27 | assert False
28 |
29 | try:
30 | img.load_file("test/resource/1.jpg")
31 | assert True
32 | except Exception:
33 | assert False
34 |
35 | def test_save_and_remove_file(self):
36 | img = ImageBasic()
37 | try:
38 | img.save_image("test/resource/1-1.jpg")
39 | assert False
40 | except ImageNoneErr:
41 | assert True
42 | except Exception:
43 | assert False
44 |
45 | try:
46 | img.load_file("test/resource/1.jpg")
47 | img.save_image("test/resource/1-1.jpg")
48 | assert isfile("test/resource/1-1.jpg")
49 | img.remove_image()
50 | assert isfile("test/resource/1.jpg") is False
51 | assert img.img is None
52 | assert True
53 | except Exception:
54 | assert False
55 |
56 | try:
57 | img.load_file("test/resource/1-1.jpg")
58 | img.save_image("test/resource/1.jpg")
59 | img.save_image("test/resource/1.jpg")
60 | assert isfile("test/resource/1.jpg")
61 | img.remove_image()
62 | assert isfile("test/resource/1.jpg")
63 | assert img.img is None
64 | assert True
65 | except Exception:
66 | assert False
67 |
68 | def test_next_photo(self):
69 | img = ImageBasic()
70 | try:
71 | img.load_file("test/resource/2.jpg")
72 | next_photo_name, index, files_nums = img.find_next_time_photo(1)
73 | assert samefile(next_photo_name, "test/resource/3.png")
74 | assert index == 3
75 | assert files_nums == 5
76 |
77 | next_photo_name, index, files_nums = img.find_next_time_photo(-1)
78 | assert samefile(next_photo_name,
79 | "test/resource/星空背景 绘画 夜空 4k壁纸_彼岸图网.jpg")
80 | assert index == 1
81 | assert files_nums == 5
82 |
83 | img.load_file("test/resource/3.png")
84 | next_photo_name, index, files_nums = img.find_next_nat_photo(1)
85 | assert samefile(next_photo_name, "test/resource/6月壁纸.jpg")
86 | assert index == 3
87 | assert files_nums == 5
88 |
89 | next_photo_name, index, files_nums = img.find_next_nat_photo(-1)
90 | assert samefile(next_photo_name, "test/resource/2.jpg")
91 | assert index == 1
92 | assert files_nums == 5
93 | assert True
94 | except Exception:
95 | assert False
96 |
97 | def test_get_img_point(self):
98 | img = ImageBasic()
99 | try:
100 | img.load_file("test/resource/2.jpg")
101 | point = img.get_img_point(214, 190)
102 | assert (point == [218, 211, 255]).all()
103 |
104 | img.load_file("test/resource/3.png")
105 | point = img.get_img_point(217, 197)
106 | assert (point == [241, 192, 190]).all()
107 | assert True
108 | except Exception:
109 | assert False
110 |
--------------------------------------------------------------------------------
/test/components/test_check_update.py:
--------------------------------------------------------------------------------
1 | from components.check_update import get_version, get_latest_version_log, get_current_version_log, check_is_latest
2 |
3 |
4 | def test_check_is_latest():
5 | assert check_is_latest('1.5.0', '1.4.0') is True
6 | assert check_is_latest('1.5.0', '1.6.0') is False
7 | assert check_is_latest('1.5.0', '1.12.0') is False
8 | assert check_is_latest('1.5.0', '12.4.0') is False
9 | assert check_is_latest('1.5.0', '0.5.1') is True
10 |
11 |
12 | TEST_VERSION_LOG = """
13 | **1.5.0**
14 |
15 | 做了一些修改
16 |
17 | **1.4.0**
18 |
19 | 做了另外一些修改
20 | """
21 |
22 |
23 | def test_get_version():
24 | assert get_version(TEST_VERSION_LOG) == '1.5.0'
25 | assert get_version(TEST_VERSION_LOG[10:]) == '1.4.0'
26 |
27 |
28 | def test_get_latest_version_log():
29 | assert get_version(get_latest_version_log()) == get_version(
30 | get_current_version_log())
31 |
32 |
33 | TEST_DOWNLOAD_LINK = 'https://imagetools.qinxing.xyz/ChromeSetup.exe'
34 | TEST_DOWNLOAD_FILE = './test.exe'
35 |
36 |
37 | def test_download():
38 | # assert download_file(TEST_DOWNLOAD_LINK) == True 下载文件OK
39 | assert True
40 |
--------------------------------------------------------------------------------
/test/resource/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/resource/1.jpg
--------------------------------------------------------------------------------
/test/resource/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/resource/2.jpg
--------------------------------------------------------------------------------
/test/resource/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/resource/3.png
--------------------------------------------------------------------------------
/test/resource/6月壁纸.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/resource/6月壁纸.jpg
--------------------------------------------------------------------------------
/test/resource/星空背景 绘画 夜空 4k壁纸_彼岸图网.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/test/resource/星空背景 绘画 夜空 4k壁纸_彼岸图网.jpg
--------------------------------------------------------------------------------
/tools/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/tools/__init__.py
--------------------------------------------------------------------------------
/tools/depth_of_focus/LenParameters.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import math
3 |
4 |
5 | class LenParameters(object):
6 | def __init__(self):
7 | '''
8 | @focus_length: 实际焦距(单位mm)
9 | @aperture: 光圈(F)
10 | @focus_distance: 对焦距离(单位mm)
11 | @effective_focus_length: 有效焦距(单位mm)
12 | @confusion_circle_diam: 弥散圈直径(单位mm)
13 | @cmos_size: sensor尺寸(单位mm)
14 | '''
15 | self.focus_length = 50
16 | self.aperture = 1.4
17 | self.focus_distance = 2000
18 | self.focus_distance_range = [0, 0]
19 | self.aperture_range = [0, 0]
20 | self.focus_range = [0, 0]
21 | self.confusion_circle_diam = 0.04
22 | self.cmos_size = 3.0
23 |
24 | def show(self):
25 | '''
26 | 调试打印
27 | '''
28 | # print('=========================================')
29 | # print('实际焦距: ' + str(self.focus_length) + ' mm')
30 | # print('光圈: F/' + str(self.aperture))
31 | # print('对焦距离: ' + str(self.focus_distance) + ' mm')
32 | # print('弥散圈直径: ' + str(self.confusion_circle_diam) + ' mm')
33 | # print('=========================================')
34 | return
35 |
36 | def calc_front_field_depth(self):
37 | """
38 | 计算前景深,有两种方法:本文用的https://wenku.baidu.com/view/2191302baf45b307e9719706.html的方法,更加准确一点
39 | """
40 | # a = self.aperture*self.confusion_circle_diam * \
41 | # self.focus_distance*self.focus_distance
42 | # b = self.focus_length*self.focus_length + self.aperture * \
43 | # self.confusion_circle_diam*self.focus_distance
44 | a = self.focus_length*self.focus_length*self.focus_distance
45 | b = self.focus_length*self.focus_length + \
46 | (self.focus_distance - self.focus_length) * \
47 | self.aperture*self.confusion_circle_diam
48 | return (a/b)
49 |
50 | def calc_back_field_depth(self):
51 | """
52 | 计算后景深
53 | """
54 | # a = self.aperture*self.confusion_circle_diam * \
55 | # self.focus_distance*self.focus_distance
56 | # b = self.focus_length*self.focus_length - self.aperture * \
57 | # self.confusion_circle_diam*self.focus_distance
58 | a = self.focus_length*self.focus_length*self.focus_distance
59 | b = self.focus_length*self.focus_length - \
60 | (self.focus_distance - self.focus_length) * \
61 | self.aperture*self.confusion_circle_diam
62 | ret = a/b
63 | # 防止后景深计算为负数
64 | if(ret <= 0):
65 | ret = float('inf')
66 | return ret
67 |
68 | def calc_confusion_circle_diam(self):
69 | '''
70 | 通过CMOS的尺寸计算弥散圈直径,默认是CMOS对角线尺寸除1000
71 | '''
72 | self.confusion_circle_diam = self.cmos_size/1000
73 | return self.confusion_circle_diam
74 |
75 | def calc_image_distance(self, step=10, unit=1000):
76 | '''
77 | 计算不同物距范围内的像距
78 | '''
79 | y = list()
80 | x = range(self.focus_distance_range[0],
81 | self.focus_distance_range[1], step*10)
82 | for self.focus_distance in x:
83 | y.append((self.focus_distance*self.focus_length) /
84 | (self.focus_distance-self.focus_length))
85 | return (x, y)
86 |
87 | def calc_field_depth(self):
88 | '''
89 | 计算总景深,后景深减去前景深
90 | '''
91 | return (self.calc_back_field_depth() - self.calc_front_field_depth())
92 |
93 | def calc_fov(self):
94 | '''
95 | 计算对角线视场角
96 | '''
97 | image_distance = (self.focus_distance*self.focus_length) / \
98 | (self.focus_distance-self.focus_length)
99 | alpha = math.atan((self.cmos_size/2)/image_distance)
100 | return (2*alpha*180/math.pi)
101 |
102 | def calc_equivalent_focus_length(self):
103 | '''
104 | 计算等效焦距
105 | '''
106 | return (43.27/self.cmos_size*self.focus_length)
107 |
108 | def calc_hyperfocal_distance(self):
109 | '''
110 | 计算超焦距,刚好后景深是无穷远时的对焦距离
111 | '''
112 | return (self.focus_length*self.focus_length/self.aperture/self.confusion_circle_diam+self.focus_length)
113 |
114 | def calc_depth_map_from_distance(self, step_num=1000, unit=1000):
115 | '''
116 | 计算不同物距范围内的景深
117 | '''
118 | y1 = list()
119 | y2 = list()
120 | x = np.linspace(self.focus_distance_range[0],
121 | self.focus_distance_range[1], step_num)
122 | for self.focus_distance in x:
123 | y1.append(self.calc_front_field_depth())
124 | y2.append(self.calc_back_field_depth())
125 | y1 = np.array(y1)/unit
126 | y2 = np.array(y2)/unit
127 | x = np.array(x)/unit
128 | return (x, y1, y2)
129 |
130 | def calc_depth_map_from_focus(self, step_num=1000, unit=1000):
131 | '''
132 | 计算不同焦距范围内的景深
133 | '''
134 | y1 = list()
135 | y2 = list()
136 | x = np.linspace(self.focus_range[0],
137 | self.focus_range[1], step_num)
138 | for self.focus_length in x:
139 | y1.append(self.calc_front_field_depth())
140 | y2.append(self.calc_back_field_depth())
141 | y1 = np.array(y1)/unit
142 | y2 = np.array(y2)/unit
143 | x = np.array(x)
144 | return (x, y1, y2)
145 |
146 | def calc_depth_map_from_apeture(self, step_num=1000, unit=1000):
147 | '''
148 | 计算不同光圈范围内的景深
149 | '''
150 | y1 = list()
151 | y2 = list()
152 | x = np.linspace(self.aperture_range[0],
153 | self.aperture_range[1], step_num)
154 | for self.aperture in x:
155 | y1.append(self.calc_front_field_depth())
156 | value = self.calc_back_field_depth()
157 | # 防止后景深计算为负数
158 | if(value <= 0):
159 | value = float('inf')
160 | y2.append(value)
161 | y1 = np.array(y1)/unit
162 | y2 = np.array(y2)/unit
163 | x = np.array(x)
164 | return (x, y1, y2)
165 |
166 |
167 | class SettingParamters(object):
168 | def __init__(self):
169 | # input setting
170 | self.input_focus_length = False
171 | self.input_apeture = False
172 | self.input_distance = False
173 | # output setting
174 | self.output_field_depth = False
175 | self.output_image_distance = False
176 | self.output_params = False
177 |
178 |
179 | cmos_size_dist = {
180 | "1/6": 3.0,
181 | "1/4": 4.0,
182 | "1/3.6": 5.0,
183 | "1/3.2": 5.678,
184 | "1/3": 6.0,
185 | "1/2.8": 6.46,
186 | "1/2.7": 6.592,
187 | "1/2.5": 7.182,
188 | "1/2": 8.000,
189 | "1/1.8": 8.933,
190 | "1/1.7": 9.500,
191 | "1/1.6": 10.07,
192 | "2/3": 11.00,
193 | "1": 16.0,
194 | "4/3": 22.5,
195 | "1.8": 25.878,
196 | "35mm film": 43.267
197 | }
198 |
--------------------------------------------------------------------------------
/tools/depth_of_focus/Readme.md:
--------------------------------------------------------------------------------
1 | # 镜头计算器
2 | ## 整体界面
3 |
4 | 
5 |
6 | 
7 |
8 | ## 主要功能
9 |
10 | ### 景深计算
11 |
12 | 1. 图像**输出**选择景深
13 |
14 | 2. 可以选择的**变量**有三种,焦距、光圈和物距,物距就是镜头的对焦距离,这个决定了左侧图像X轴的意义
15 |
16 | 3. **基础设置**:设置镜头的焦距,光圈,物体对焦距离和sensor尺寸,这四个参数必须准确
17 |
18 | 4. 高级设置—**sensor尺寸**:由于sensor尺寸用英寸表示,并不准确,这里提供sensor尺寸的微调。
19 |
20 | 5. 高级设置—**弥散圈直径**:景深就是图像清晰的范围,而弥散圈就是衡量图像清晰的标准。滑块往左拖,弥散圈越小,那么清晰度的标准就越高,景深对应也会越小。
21 |
22 | 6. **高级设置**:焦距、光圈、对焦范围,是用来调节图像X轴的范围
23 |
24 | 7. 最后点击**确认**就会输出想要的图像
25 |
26 | 8. 图像下方有对图像微调的**按钮**
27 |
28 | 从左到右的功能分别是还原图像,回退上一步,前进一步,移动图像,图像局部放大,调整图像坐标轴的范围,调整图像坐标轴的名称,另存图像
29 |
30 | ### 参数输出
31 |
32 | 1. 图像输出选择参数
33 | 2. 其余步骤参考景深计算一致
34 | 3. 等效焦距:sensor对角线的长度,等效成35mm照相机画幅对角线长度(42.27mm)时,其镜头的实际焦距所对应的35mm照相机镜头的焦距。
35 | 4. 超焦距距离:对焦距离越近,前景深越近。而对焦在远处的某一点,使得景深的另一极端恰为”无限远“,此时对焦距离就称作超焦距距离。相机对焦在超焦距距离,可以让景深最大
36 | 5. 前景深距离:当前配置下,能看清的最近距离
37 | 6. 后景深距离:当前配置下,能看清的最远距离
38 | 7. 总景深:当前配置下,清晰的范围
39 |
40 | ## 计算原理
41 |
42 | 本文用的方法来自此[论文](https://wenku.baidu.com/view/2191302baf45b307e9719706.html)
43 |
44 |
--------------------------------------------------------------------------------
/tools/depth_of_focus/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/tools/depth_of_focus/__init__.py
--------------------------------------------------------------------------------
/tools/depth_of_focus/depth_of_focus.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | from tools.depth_of_focus.field_depth_window import Ui_FieldDepthWindow
3 | from tools.depth_of_focus.LenParameters import LenParameters, SettingParamters, cmos_size_dist
4 | from components.customwidget import MatplotlibLayout, ParamsTable
5 | from components.window import SubWindow
6 |
7 |
8 | class FieldDepthWindow(SubWindow):
9 | def __init__(self, name='FieldDepthWindow', parent=None):
10 | super().__init__(name, parent, Ui_FieldDepthWindow())
11 | [self.params, self.setting] = self.load_params(
12 | [LenParameters(), SettingParamters()])
13 | self.init_params_range()
14 |
15 | def show(self):
16 | super().show()
17 | self.ui.pushButton.clicked.connect(self.finished_plot_cb)
18 | self.ui.sensor_size.editingFinished.connect(self.coms_size_changed_cb)
19 | self.ui.confusion_circle_diam_slide.sliderMoved.connect(
20 | self.confusion_circle_diam_changed_cb)
21 | self.ui.sensor_size_list.currentTextChanged.connect(
22 | self.coms_size_list_changed_cb)
23 |
24 | self.set_ui_params()
25 |
26 | self.plot_fig = MatplotlibLayout(self.ui.plotview)
27 | self.tableWidget = ParamsTable(self.ui.plotview)
28 | self.finished_plot_cb()
29 |
30 | def init_params_range(self):
31 | self.params.aperture_range[0] = self.params.aperture/2
32 | self.params.aperture_range[1] = self.params.aperture*2
33 | self.params.focus_distance_range[0] = self.params.focus_distance/2
34 | self.params.focus_distance_range[1] = self.params.focus_distance*2
35 | self.params.focus_range[0] = self.params.focus_length/2
36 | self.params.focus_range[1] = self.params.focus_length*2
37 |
38 | def plot_figure(self):
39 | self.plot_fig.clean()
40 | if(self.setting.output_field_depth == True):
41 | if(self.setting.input_distance == True):
42 | (x, y1, y2) = self.params.calc_depth_map_from_distance()
43 | self.plot_fig.label("对焦距离(m)", "景深范围(m)")
44 | elif(self.setting.input_focus_length == True):
45 | (x, y1, y2) = self.params.calc_depth_map_from_focus()
46 | self.plot_fig.label("焦距(mm)", "景深范围(m)")
47 | elif(self.setting.input_apeture == True):
48 | (x, y1, y2) = self.params.calc_depth_map_from_apeture()
49 | self.plot_fig.label("光圈值(F)", "景深范围(m)")
50 | self.plot_fig.input_2line(x, y1, y2)
51 | self.plot_fig.draw()
52 |
53 | def plot_params(self):
54 | self.tableWidget.clean()
55 | self.tableWidget.append("视场角", str(self.params.calc_fov()), '度')
56 | self.tableWidget.append("等效焦距", str(
57 | self.params.calc_equivalent_focus_length()), 'mm')
58 | self.tableWidget.append("超焦距距离", str(
59 | self.params.calc_hyperfocal_distance()/1000), 'm')
60 | self.tableWidget.append("前景深", str(
61 | self.params.calc_front_field_depth()/1000), 'm')
62 | self.tableWidget.append("后景深", str(
63 | self.params.calc_back_field_depth()/1000), 'm')
64 | self.tableWidget.append("总景深", str(
65 | self.params.calc_field_depth()/1000), 'm')
66 | self.tableWidget.show()
67 |
68 | def calc_len_params(self):
69 | # print('视场角:'+str(self.params.calc_fov())+'度')
70 | # print('等效焦距:'+str(self.params.calc_equivalent_focus_length())+'mm')
71 | # print('超焦距距离:'+str(self.params.calc_hyperfocal_distance()/1000) + 'm')
72 | # print("前景深:"+str(self.params.calc_front_field_depth()/1000) + 'm')
73 | # print("后景深:"+str(self.params.calc_back_field_depth()/1000) + 'm')
74 | # print("总景深:" + str(self.params.calc_field_depth()/1000)+'m')
75 | return
76 |
77 | # get params
78 | def get_ui_params(self):
79 | # basic setting
80 | self.params.focus_length = float(self.ui.focus_length.value())
81 | self.params.confusion_circle_diam = float(
82 | self.ui.confusion_circle_diam.value())
83 | self.params.aperture = float(self.ui.aperture.value())
84 | self.params.focus_distance = float(self.ui.focus_distance.value())*1000
85 | self.params.cmos_size = float(self.ui.sensor_size.value())
86 | # input setting
87 | self.setting.input_focus_length = self.ui.input_focus_length.isChecked()
88 | self.setting.input_apeture = self.ui.input_apeture.isChecked()
89 | self.setting.input_distance = self.ui.input_distance.isChecked()
90 | # output setting
91 | self.setting.output_field_depth = self.ui.output_field_depth.isChecked()
92 | self.setting.output_image_distance = self.ui.output_image_distance.isChecked()
93 | self.setting.output_params = self.ui.output_params.isChecked()
94 | # advanced setting
95 | self.params.aperture_range[0] = float(
96 | self.ui.apeture_min_range.value())
97 | self.params.aperture_range[1] = float(
98 | self.ui.apeture_max_range.value())
99 | self.params.focus_distance_range[0] = float(
100 | self.ui.distance_min_range.value())*1000
101 | self.params.focus_distance_range[1] = float(
102 | self.ui.distance_max_range.value())*1000
103 | self.params.focus_range[0] = float(self.ui.focus_min_range.value())
104 | self.params.focus_range[1] = float(self.ui.focus_max_range.value())
105 |
106 | def set_ui_params(self):
107 | # basic setting
108 | self.ui.focus_length.setValue(self.params.focus_length)
109 | self.ui.confusion_circle_diam.setValue(
110 | self.params.confusion_circle_diam)
111 | self.ui.aperture.setValue(self.params.aperture)
112 | self.ui.focus_distance.setValue(self.params.focus_distance / 1000)
113 | self.ui.sensor_size.setValue(self.params.cmos_size)
114 |
115 | # input setting
116 | self.ui.input_focus_length.setChecked(self.setting.input_focus_length)
117 | self.ui.input_apeture.setChecked(self.setting.input_apeture)
118 | self.ui.input_distance.setChecked(self.setting.input_distance)
119 | # output setting
120 | self.ui.output_field_depth.setChecked(self.setting.output_field_depth)
121 | self.ui.output_image_distance.setChecked(
122 | self.setting.output_image_distance)
123 | self.ui.output_params.setChecked(self.setting.output_params)
124 |
125 | # advanced setting
126 | self.ui.apeture_min_range.setValue(self.params.aperture_range[0])
127 | self.ui.apeture_max_range.setValue(self.params.aperture_range[1])
128 | self.ui.distance_min_range.setValue(
129 | self.params.focus_distance_range[0]/1000)
130 | self.ui.distance_max_range.setValue(
131 | self.params.focus_distance_range[1]/1000)
132 | self.ui.focus_min_range.setValue(self.params.focus_range[0])
133 | self.ui.focus_max_range.setValue(self.params.focus_range[1])
134 |
135 | # CALLBACKS
136 | def finished_plot_cb(self):
137 | self.get_ui_params()
138 | if self.setting.output_field_depth == True:
139 | self.tableWidget.clean()
140 | self.plot_figure()
141 | elif self.setting.output_params == True:
142 | self.plot_fig.clean()
143 | self.plot_params()
144 | self.calc_len_params()
145 |
146 | def coms_size_list_changed_cb(self):
147 | self.params.cmos_size = cmos_size_dist[self.ui.sensor_size_list.currentText(
148 | )]
149 | self.ui.sensor_size.setValue(self.params.cmos_size)
150 | self.confusion_circle_diam_changed_cb()
151 |
152 | def coms_size_changed_cb(self):
153 | self.params.cmos_size = self.ui.sensor_size.value()
154 | self.confusion_circle_diam_changed_cb()
155 |
156 | def confusion_circle_diam_changed_cb(self):
157 | value = self.ui.confusion_circle_diam_slide.value()
158 | self.params.confusion_circle_diam = self.params.cmos_size / \
159 | 1000*(0.5+value/100)
160 | self.ui.confusion_circle_diam.setValue(
161 | self.params.confusion_circle_diam)
162 |
--------------------------------------------------------------------------------
/tools/imageeditor/Readme.md:
--------------------------------------------------------------------------------
1 | # 图片查看工具
2 | 这是一款能够对图像进行分析以及简单的处理工具
3 | ## 界面
4 | 1. 右侧有四个按钮,分为为:打开图像,保存图像,图像处理前后对比,图像统计分析
5 | 2. 菜单栏有各种图像处理的模块,目前仅仅做了滤波的相关处理
6 | ## 使用方法
7 | 1. 可以通过点击打开图像的按钮或者拖动图片到图像显示框里进行显示
8 | 2. 通过菜单栏可以对图像进行简单的处理
9 | 3. 通过保存按钮可以保存当前的图像(可是是图像处理前后的)
10 | 4. 通过对比按钮可以对比图像处理前后的变化
11 | 5. 通过点击图像统计分析按钮可以获取到整幅图像的统计信息,包括直方图,RGB均值与信噪比,转成YUV之后的均值和信噪比,窗口大小,以及RGB增益
12 | 6. 统计信息窗口不关闭的情况下,框选图像中的任意一块区域,都可以显示这块区域的统计信息
13 | 7. 统计信息窗口中的直方图,显示了RGB和Y通道的直方图,可以通过复选框选择是否显示该通道的直方图
--------------------------------------------------------------------------------
/tools/imageeditor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/tools/imageeditor/__init__.py
--------------------------------------------------------------------------------
/tools/imageeditor/imageeffect.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 | from components.customwidget import critical_win
4 |
5 |
6 | class WaterMarkType():
7 | """
8 | 水印的类型
9 | """
10 | NoWaterMark = -1
11 | TransparentWaterMark = 0
12 | SpaceWaterMark = 1
13 | FrequencyWaterMark = 2
14 |
15 |
16 | class WaterMarkParams():
17 | """
18 | 水印的参数
19 | transparent:透明度 范围:0-100
20 | watermark_type:水印的类型 范围:WaterMarkType
21 | threshold:水印二值化的阈值 范围:0-255
22 | size:水印的缩放大小 范围:0-1000
23 | """
24 | transparent = 0
25 | watermark_type = WaterMarkType.NoWaterMark
26 | threshold = 50
27 | size = 100
28 | filename = ""
29 |
30 |
31 | class ImageEffect(object):
32 | nowImage = None
33 | img_index = 0
34 |
35 | def __init__(self):
36 | self.is_load_image = False
37 |
38 | def load_image(self, filename):
39 | # 防止有中文
40 | # self.srcImage = cv2.imread(filename)
41 | self.srcImage = cv2.imdecode(np.fromfile(filename, dtype=np.uint8), 1)
42 |
43 | # 根据不同的颜色通道,进行不同的颜色转换
44 | # if(self.depth == 3):
45 | # self.srcImage = cv2.cvtColor(self.srcImage, cv2.COLOR_BGR2RGB)
46 | # elif(self.depth == 4):
47 | # self.srcImage = cv2.cvtColor(self.srcImage, cv2.COLOR_BGR2BGRA)
48 | # else:
49 | # self.srcImage == None
50 | self.depth = 0
51 | if (self.srcImage is not None):
52 | self.height = self.srcImage.shape[0]
53 | self.width = self.srcImage.shape[1]
54 | self.depth = self.srcImage.shape[2]
55 | self.dstImage = self.srcImage.copy()
56 | self.nowImage = self.srcImage
57 | self.is_load_image = True
58 |
59 | def save_image(self, img, filename):
60 | # cv2.imwrite(filename, self.nowImage)
61 | # 解决中文路径的问题
62 | cv2.imencode('.jpg', self.nowImage)[1].tofile(filename)
63 |
64 | def imageconvert(self, index):
65 | """
66 | func: 切换需要操作的img, 0 为原图,1为目标图
67 | """
68 | if (index == 0):
69 | self.nowImage = self.srcImage
70 | self.img_index = 0
71 | elif (index == 1):
72 | self.nowImage = self.dstImage
73 | self.img_index = 1
74 |
75 | def get_img_index(self):
76 | return self.img_index
77 |
78 | def get_img_point(self, x, y):
79 | """
80 | 获取图像中一个点的RGB值,注意颜色顺序是BGR
81 | """
82 | if(x > 0 and x < self.width and y > 0 and y < self.height):
83 | return self.nowImage[y, x]
84 | else:
85 | return None
86 |
87 | def get_src_image(self):
88 | return self.srcImage
89 |
90 | def get_dst_image(self):
91 | return self.dstImage
92 |
93 | def blur(self, type):
94 | if (type == BlurType.BoxBlur):
95 | self.dstImage = cv2.boxFilter(self.srcImage, -1, BlurType.BlurSize)
96 | elif (type == BlurType.GaussianBlur):
97 | self.dstImage = cv2.GaussianBlur(
98 | self.srcImage, BlurType.BlurSize, 0, 0)
99 | elif (type == BlurType.MediaBlur):
100 | self.dstImage = cv2.medianBlur(self.srcImage, BlurType.medianSize)
101 | elif (type == BlurType.BilateralBlur):
102 | self.dstImage = cv2.bilateralFilter(self.srcImage, BlurType.BilateralSize,
103 | BlurType.BilateralSize * 2, BlurType.BilateralSize / 2)
104 |
105 | def set_watermark_img(self, filename):
106 | self.watermark_img = cv2.imdecode(
107 | np.fromfile(filename, dtype=np.uint8), -1)
108 | if (self.watermark_img is not None):
109 | self.watermark_img_height = self.watermark_img.shape[0]
110 | self.watermark_img_width = self.watermark_img.shape[1]
111 | else:
112 | critical_win('水印图片无法打开')
113 |
114 | def set_watermark_show(self, params: WaterMarkParams):
115 | """
116 | func: 设置水印的显示
117 | 先进行水印的缩放,如果是空域水印,就进行转换成灰度图二值化,如果是半透明水印,就进行透明化处理,最后和原图进行融合
118 | """
119 | if(params.watermark_type != WaterMarkType.NoWaterMark):
120 | watermark_img_tmp = self.watermark_img.copy()
121 | self.dstImage = self.srcImage.copy()
122 | if(params.size <= 1000 and params.size > 0):
123 | watermark_img_tmp = cv2.resize(watermark_img_tmp, (int(
124 | self.watermark_img_width*params.size/100), int(self.watermark_img_height*params.size/100)), interpolation=cv2.INTER_AREA)
125 | if(params.watermark_type == WaterMarkType.SpaceWaterMark and params.threshold <= 255 and params.threshold >= 0):
126 | watermark_img_tmp = cv2.cvtColor(
127 | watermark_img_tmp, cv2.COLOR_BGR2GRAY)
128 | watermark_img_tmp = cv2.threshold(
129 | watermark_img_tmp, params.threshold, 255, cv2.THRESH_BINARY)[1]
130 | watermark_img_tmp = cv2.cvtColor(
131 | watermark_img_tmp, cv2.COLOR_GRAY2BGR)
132 | width = min(watermark_img_tmp.shape[1], self.width)
133 | height = min(watermark_img_tmp.shape[0], self.height)
134 | w_start = int((self.width - width)/2)
135 | h_start = int((self.height - height)/2)
136 | clip_img = self.dstImage[h_start:height +
137 | h_start, w_start:width+w_start]
138 | if(params.watermark_type == WaterMarkType.TransparentWaterMark and params.transparent <= 100 and params.transparent >= 0):
139 | self.dstImage[h_start:height+h_start, w_start:width+w_start] = cv2.addWeighted(
140 | watermark_img_tmp[:height, :width], 1-params.transparent/100, clip_img, params.transparent/100, 0)
141 | else:
142 | self.dstImage[h_start:height+h_start, w_start:width +
143 | w_start] = watermark_img_tmp[:height, :width]
144 |
145 | def generate_watermark(self, params: WaterMarkParams):
146 | if(params.watermark_type == WaterMarkType.SpaceWaterMark):
147 | watermark_img_tmp = self.watermark_img.copy()
148 | self.dstImage = self.srcImage.copy()
149 | watermark_img_tmp = cv2.cvtColor(
150 | watermark_img_tmp, cv2.COLOR_BGR2GRAY)
151 | watermark_img_tmp = cv2.threshold(
152 | watermark_img_tmp, params.threshold, 1, cv2.THRESH_BINARY)[1]
153 | width = min(watermark_img_tmp.shape[1], self.width)
154 | height = min(watermark_img_tmp.shape[0], self.height)
155 | w_start = int((self.width - width)/2)
156 | h_start = int((self.height - height)/2)
157 | self.dstImage[h_start:height+h_start,
158 | w_start:width+w_start, 0] &= 254
159 | self.dstImage[h_start:height+h_start, w_start:width +
160 | w_start, 0] |= watermark_img_tmp[:height, :width]
161 |
162 | def analysis_space_watermark(self):
163 | tmp = cv2.threshold(
164 | self.nowImage[:, :, 0], 0, 255, cv2.THRESH_BINARY)[1]
165 | self.dstImage = cv2.cvtColor(tmp, cv2.COLOR_GRAY2BGR)
166 |
167 |
168 | class BlurType():
169 | BoxBlur = 0
170 | GaussianBlur = 1
171 | MediaBlur = 2
172 | BilateralBlur = 3
173 | BlurSize = (5, 5)
174 | medianSize = 5
175 | BilateralSize = 25
176 |
--------------------------------------------------------------------------------
/tools/imageeditor/ui/imageeditor_window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | ImageEditor
4 |
5 |
6 |
7 | 0
8 | 0
9 | 800
10 | 600
11 |
12 |
13 |
14 | 图片处理工具
15 |
16 |
17 |
18 | :/tool_icon/resource/main.png:/tool_icon/resource/main.png
19 |
20 |
21 |
22 |
23 |
24 | -1
25 |
26 |
27 | true
28 |
29 |
30 | QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks|QMainWindow::GroupedDragging|QMainWindow::VerticalTabs
31 |
32 |
33 |
34 | -
35 |
36 |
37 |
38 |
39 |
40 |
-
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
81 |
82 |
83 | true
84 |
85 |
86 |
87 |
88 |
89 | 0
90 | 0
91 |
92 |
93 |
94 | toolBar_2
95 |
96 |
97 | Qt::AllToolBarAreas
98 |
99 |
100 | Qt::Vertical
101 |
102 |
103 | RightToolBarArea
104 |
105 |
106 | false
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | 直方图
119 |
120 |
121 |
122 |
123 | true
124 |
125 |
126 | 统计信息
127 |
128 |
129 |
130 |
131 | 方框滤波
132 |
133 |
134 |
135 |
136 | 高斯滤波
137 |
138 |
139 |
140 |
141 |
142 | :/tool_icon/resource/save_icon.png:/tool_icon/resource/save_icon.png
143 |
144 |
145 | save
146 |
147 |
148 | Ctrl+S
149 |
150 |
151 |
152 |
153 | 中值滤波
154 |
155 |
156 | Qt::WindowShortcut
157 |
158 |
159 |
160 |
161 | 双边滤波
162 |
163 |
164 |
165 |
166 |
167 | :/tool_icon/resource/compare.png:/tool_icon/resource/compare.png
168 |
169 |
170 | compare
171 |
172 |
173 | compare
174 |
175 |
176 |
177 |
178 |
179 | :/tool_icon/resource/open.png:/tool_icon/resource/open.png
180 |
181 |
182 | open
183 |
184 |
185 | open
186 |
187 |
188 |
189 |
190 |
191 | :/tool_icon/resource/stats.png:/tool_icon/resource/stats.png
192 |
193 |
194 | stats
195 |
196 |
197 | 统计信息
198 |
199 |
200 |
201 |
202 | 制作水印
203 |
204 |
205 |
206 |
207 | 解析水印
208 |
209 |
210 |
211 |
212 | 水印制作
213 |
214 |
215 |
216 |
217 | 水印制作
218 |
219 |
220 |
221 |
222 |
223 | :/tool_icon/resource/down.png:/tool_icon/resource/down.png
224 |
225 |
226 | 下一个图片
227 |
228 |
229 | Down
230 |
231 |
232 |
233 |
234 |
235 | :/tool_icon/resource/up.png:/tool_icon/resource/up.png
236 |
237 |
238 | 上一张图片
239 |
240 |
241 | Up
242 |
243 |
244 |
245 |
246 |
247 | :/tool_icon/resource/delete.png:/tool_icon/resource/delete.png
248 |
249 |
250 | 删除
251 |
252 |
253 | 删除
254 |
255 |
256 | Del
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
--------------------------------------------------------------------------------
/tools/imageeditor/ui/watermarkview.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'watermarkview.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.1
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import *
12 | from PySide2.QtGui import *
13 | from PySide2.QtWidgets import *
14 |
15 |
16 | class Ui_WaterMarkView(object):
17 | def setupUi(self, WaterMarkView):
18 | if not WaterMarkView.objectName():
19 | WaterMarkView.setObjectName(u"WaterMarkView")
20 | WaterMarkView.resize(252, 327)
21 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
22 | sizePolicy.setHorizontalStretch(0)
23 | sizePolicy.setVerticalStretch(0)
24 | sizePolicy.setHeightForWidth(WaterMarkView.sizePolicy().hasHeightForWidth())
25 | WaterMarkView.setSizePolicy(sizePolicy)
26 | WaterMarkView.setSizeGripEnabled(True)
27 | self.gridLayout = QGridLayout(WaterMarkView)
28 | self.gridLayout.setObjectName(u"gridLayout")
29 | self.groupBox = QGroupBox(WaterMarkView)
30 | self.groupBox.setObjectName(u"groupBox")
31 | self.groupBox.setMaximumSize(QSize(16777215, 16777215))
32 | self.gridLayout_9 = QGridLayout(self.groupBox)
33 | self.gridLayout_9.setObjectName(u"gridLayout_9")
34 | self.analysis = QPushButton(self.groupBox)
35 | self.analysis.setObjectName(u"analysis")
36 |
37 | self.gridLayout_9.addWidget(self.analysis, 2, 1, 1, 1)
38 |
39 | self.generate = QPushButton(self.groupBox)
40 | self.generate.setObjectName(u"generate")
41 |
42 | self.gridLayout_9.addWidget(self.generate, 1, 1, 1, 1)
43 |
44 | self.gridLayout_8 = QGridLayout()
45 | self.gridLayout_8.setObjectName(u"gridLayout_8")
46 | self.label = QLabel(self.groupBox)
47 | self.label.setObjectName(u"label")
48 |
49 | self.gridLayout_8.addWidget(self.label, 1, 0, 1, 1)
50 |
51 | self.change_transparent = QSlider(self.groupBox)
52 | self.change_transparent.setObjectName(u"change_transparent")
53 | self.change_transparent.setMaximum(100)
54 | self.change_transparent.setOrientation(Qt.Horizontal)
55 |
56 | self.gridLayout_8.addWidget(self.change_transparent, 4, 1, 1, 1)
57 |
58 | self.label_2 = QLabel(self.groupBox)
59 | self.label_2.setObjectName(u"label_2")
60 |
61 | self.gridLayout_8.addWidget(self.label_2, 4, 0, 1, 1)
62 |
63 | self.label_19 = QLabel(self.groupBox)
64 | self.label_19.setObjectName(u"label_19")
65 |
66 | self.gridLayout_8.addWidget(self.label_19, 3, 0, 1, 1)
67 |
68 | self.change_watermark_size = QSlider(self.groupBox)
69 | self.change_watermark_size.setObjectName(u"change_watermark_size")
70 | self.change_watermark_size.setMinimum(1)
71 | self.change_watermark_size.setMaximum(500)
72 | self.change_watermark_size.setValue(100)
73 | self.change_watermark_size.setOrientation(Qt.Horizontal)
74 |
75 | self.gridLayout_8.addWidget(self.change_watermark_size, 3, 1, 1, 1)
76 |
77 | self.change_watermark_th = QSlider(self.groupBox)
78 | self.change_watermark_th.setObjectName(u"change_watermark_th")
79 | self.change_watermark_th.setMaximum(255)
80 | self.change_watermark_th.setValue(127)
81 | self.change_watermark_th.setOrientation(Qt.Horizontal)
82 |
83 | self.gridLayout_8.addWidget(self.change_watermark_th, 2, 1, 1, 1)
84 |
85 | self.change_watermark_type = QComboBox(self.groupBox)
86 | self.change_watermark_type.addItem("")
87 | self.change_watermark_type.addItem("")
88 | self.change_watermark_type.addItem("")
89 | self.change_watermark_type.setObjectName(u"change_watermark_type")
90 |
91 | self.gridLayout_8.addWidget(self.change_watermark_type, 1, 1, 1, 1)
92 |
93 | self.label_18 = QLabel(self.groupBox)
94 | self.label_18.setObjectName(u"label_18")
95 |
96 | self.gridLayout_8.addWidget(self.label_18, 2, 0, 1, 1)
97 |
98 | self.open_watermark = QPushButton(self.groupBox)
99 | self.open_watermark.setObjectName(u"open_watermark")
100 |
101 | self.gridLayout_8.addWidget(self.open_watermark, 0, 0, 1, 1)
102 |
103 | self.watermark_path = QLineEdit(self.groupBox)
104 | self.watermark_path.setObjectName(u"watermark_path")
105 |
106 | self.gridLayout_8.addWidget(self.watermark_path, 0, 1, 1, 1)
107 |
108 |
109 | self.gridLayout_9.addLayout(self.gridLayout_8, 0, 1, 1, 1)
110 |
111 |
112 | self.gridLayout.addWidget(self.groupBox, 0, 1, 1, 1)
113 |
114 |
115 | self.retranslateUi(WaterMarkView)
116 |
117 | QMetaObject.connectSlotsByName(WaterMarkView)
118 | # setupUi
119 |
120 | def retranslateUi(self, WaterMarkView):
121 | WaterMarkView.setWindowTitle(QCoreApplication.translate("WaterMarkView", u"\u6c34\u5370\u5236\u4f5c\u5de5\u5177", None))
122 | self.groupBox.setTitle(QCoreApplication.translate("WaterMarkView", u"\u5236\u4f5c\u6c34\u5370", None))
123 | self.analysis.setText(QCoreApplication.translate("WaterMarkView", u"\u89e3\u6790\u7a7a\u57df\u9690\u5f62\u6c34\u5370", None))
124 | self.generate.setText(QCoreApplication.translate("WaterMarkView", u"\u5236\u4f5c\u5e26\u6c34\u5370\u7684\u56fe\u7247", None))
125 | self.label.setText(QCoreApplication.translate("WaterMarkView", u"\u6c34\u5370\u7c7b\u578b", None))
126 | self.label_2.setText(QCoreApplication.translate("WaterMarkView", u"\u900f\u660e\u7a0b\u5ea6", None))
127 | self.label_19.setText(QCoreApplication.translate("WaterMarkView", u"\u8c03\u6574\u6c34\u5370\u5927\u5c0f", None))
128 | self.change_watermark_type.setItemText(0, QCoreApplication.translate("WaterMarkView", u"\u534a\u900f\u660e\u6c34\u5370", None))
129 | self.change_watermark_type.setItemText(1, QCoreApplication.translate("WaterMarkView", u"\u7a7a\u57df\u9690\u5f62\u6c34\u5370", None))
130 | self.change_watermark_type.setItemText(2, QCoreApplication.translate("WaterMarkView", u"\u9891\u57df\u9690\u5f62\u6c34\u5370", None))
131 |
132 | self.label_18.setText(QCoreApplication.translate("WaterMarkView", u"\u4e8c\u503c\u5316", None))
133 | self.open_watermark.setText(QCoreApplication.translate("WaterMarkView", u"\u5bfc\u5165\u6c34\u5370", None))
134 | # retranslateUi
135 |
136 |
--------------------------------------------------------------------------------
/tools/imageeditor/ui/watermarkview.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | WaterMarkView
4 |
5 |
6 |
7 | 0
8 | 0
9 | 252
10 | 327
11 |
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 | 水印制作工具
21 |
22 |
23 | true
24 |
25 |
26 | -
27 |
28 |
29 |
30 | 16777215
31 | 16777215
32 |
33 |
34 |
35 | 制作水印
36 |
37 |
38 |
-
39 |
40 |
41 | 解析空域隐形水印
42 |
43 |
44 |
45 | -
46 |
47 |
48 | 制作带水印的图片
49 |
50 |
51 |
52 | -
53 |
54 |
-
55 |
56 |
57 | 水印类型
58 |
59 |
60 |
61 | -
62 |
63 |
64 | 100
65 |
66 |
67 | Qt::Horizontal
68 |
69 |
70 |
71 | -
72 |
73 |
74 | 透明程度
75 |
76 |
77 |
78 | -
79 |
80 |
81 | 调整水印大小
82 |
83 |
84 |
85 | -
86 |
87 |
88 | 1
89 |
90 |
91 | 500
92 |
93 |
94 | 100
95 |
96 |
97 | Qt::Horizontal
98 |
99 |
100 |
101 | -
102 |
103 |
104 | 255
105 |
106 |
107 | 127
108 |
109 |
110 | Qt::Horizontal
111 |
112 |
113 |
114 | -
115 |
116 |
-
117 |
118 | 半透明水印
119 |
120 |
121 | -
122 |
123 | 空域隐形水印
124 |
125 |
126 | -
127 |
128 | 频域隐形水印
129 |
130 |
131 |
132 |
133 | -
134 |
135 |
136 | 二值化
137 |
138 |
139 |
140 | -
141 |
142 |
143 | 导入水印
144 |
145 |
146 |
147 | -
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/tools/rawimageeditor/Readme.md:
--------------------------------------------------------------------------------
1 | # RAW图片编辑工具
2 | 这是一款能够对RAW图进行解析和ISP处理的工具。可以方便的进行后期算法的验证(热更新、直观的图像显示和分析)。退出后会自动保存上次打开的窗口,以及窗口里的参数。同时将界面与处理线程分离,让界面更加流畅。
3 |
4 | ### 界面
5 |
6 | 
7 |
8 | 1. 左侧是图像预览窗口
9 | 2. 右上角有两个窗口,左边的是RAW图的设置,右边的是ISP处理流程
10 | 3. 右下角是对ISP处理的一些配置
11 |
12 | ### 使用方法
13 |
14 | 1. **导入raw图**:先进行RAW图的设置,然后可以点击“打开图片”或者拖拽的方式打开图片,此时图片预览窗口显示的是RAW图,可以用鼠标进行放大缩小和移动,窗口的左下角会显示每个点的值,以及缩放比例
15 | 2. **ISP pipeline设置**:可以通过`勾选`的方式去启用部分ISP流程,通过`拖拽`的方式去调整ISP的顺序,然后点击确定按钮,可以进行ISP的处理,右下角的进度条可以显示ISP处理的进度。
16 | 3. **ISP 参数设置**:右下角的ISP参数设置窗口,修改参数后,也需要点击确定按钮,运行ISP。第二步和第三步ISP的处理,会对比之前的ISP流程,自动搜索最少的处理流程。右下角有处理的进度条以及耗时显示。
17 | 4. **过程中的图片查看**:`双击`ISP处理流程中的模块,可以看到经过这个模块的处理,图片变成了什么效果。此功能可以方便的看到每个ISP模块的效果。
18 | 5. **图片分析**:图片查看时点击`图片分析`按钮,会立刻显示全局的直方图统计,图片大小,平均值等信息。此时鼠标变成了选框模式,只要选中图片中的某一区域,就会显示这个区域的直方图信息,信噪比,平均值,RGB比值等信息,直方图可以挑选YRGB中的任意通道进行显示。
19 | 6. **算法调试**:如果需要进行算法的调试,修改相关ISP算法代码之后,点击`算法热更新`,可以重新加载ISP相关算法(在isp.py中),不需要重新启动程序。如果报错,弹出的窗口会显示错误信息。
20 |
21 | ### 参数设置
22 |
23 | 1. 图片格式:RAW图的宽,高,bit位数,格式和pattern,目前仅支持海思的raw图格式
24 | 2. ISP处理流程:ISP处理分为三个区域,绿色的是raw域的处理,黄色的是RGB域的处理,蓝色的是yuv域的处理。可以通过勾选的方式去启用部分ISP流程,通过拖拽的方式去调整ISP的顺序,但是调整不要超过自己的区域,如raw域的处理不能放在yuv域进行处理。
25 | 3. 黑电平:每个通道的黑电平
26 | 4. 坏点检测:调整检测的区域
27 | 5. rolloff:暗影矫正
28 | 导入拍摄均匀光照的raw图,格式要一样
29 | 6. demosaic:去马赛克
30 | 有三种模式:双线性插值、malver、menon实现
31 | 7. awb: 白平衡
32 | 填入RGB的增益,也可以点击`从raw图选取`的按钮,然后用鼠标在图中选中一块灰色的区域即可,注意用来选取白平衡的图,需要是在黑电平处理过后的raw图
33 | 8. ccm: 3x3色彩校正矩阵
34 | 9. gamma:gamma值
35 | 10. LTM:局部对比度增强
36 | 1. 暗区提升:提升暗区亮度
37 | 2. 亮度抑制:抑制亮度亮度
38 | 11. CSC:色度空间转换RGB->YUV
39 | 1. 是否限制YUV输出范围:TV标准中亮度的范围是16-235,PC标准是0-255,如果选中,就是采用TV标准,对比度会低一些
40 | 2. 色域标准:BT709,BT2020, BT601三种可选
41 | 3. 亮度、对比度、色调、饱和度调整
42 | 12. yuv denoise: 在三种频率上分别进行小波降噪
43 | 1. 降噪强度:双边滤波的强度,值越大,降噪强度越大
44 | 2. 噪声阈值:值越大,选取的噪声范围越大
45 | 3. 降噪权重:值越大,降噪越强,值为0的时候,就不进行降噪
46 | 4. 色度降噪强度:值越大,颜色降噪越强
47 | 13. yuv sharpen: 基于方向检测的锐化
48 | 1. 3x3中值滤波强度:在锐化强进行一次中值滤波,值越大,滤波越强,值为0,就是没有进行中值滤波
49 | 2. 锐化强度:值越大,锐化强度越大
50 | 3. 锐化钳位阈值:控制锐化上限,避免出现白边,值越小,锐化上限越低
51 | 4. 降噪阈值:小于这个范围的细节进行降噪,大于这个范围的细节进行锐化,值越大,降噪的范围越多
52 |
53 | ### 目前进展
54 |
55 | 目前实现了黑电平,坏点矫正,暗影矫正,去马赛克,白平衡,色彩校正,gamma,局部对比度增强,色度空间转换,对比度亮度调整,小波降噪WNR,锐化等算法
56 |
57 | ## 软件架构
58 |
59 |
60 |
61 |
62 |
63 | ``` mermaid
64 | classDiagram
65 | RawImageEditor --|> SubWindow
66 | RawImageEditor *--> RawImageParams
67 | RawImageEditor *--> ISPpipeline
68 | ISPpipeline *--> RawImageInfo
69 | ISPpipeline o--> RawImageParams
70 | ISPpipeline *--> ISPProc
71 | ISPProc ..> ispfunction
72 | RawImageParams o--> RawImageParams : instance
73 |
74 | SubWindow: imagetools子窗口
75 | SubWindow: 实现参数缓存功能
76 | ISPpipeline: ISP处理流程
77 | RawImageEditor: 主窗口
78 | RawImageParams: ISP相关参数设置
79 | RawImageInfo: 图片属性
80 | RawImageInfo: 图片显示等操作方法
81 | RawImageInfo: 包括RAW/RGB/YUV
82 | ISPProc: ISP后台处理线程
83 | ISPProc: 自动搜索最短过程
84 | ispfunction: isp算法实现
85 | ```
86 |
87 | 主窗口继承的是Subwindow,实现了参数缓存的功能,里面创建了参数类RawImageParams和ISP处理流程类ISPpipeline。
88 |
89 | 参数类RawImageParams主要是界面上ISP相关参数的设置,组合了RAW图的格式,黑电平,去马赛克等过程的参数配置。此参数配置会缓存到本地,以便下次使用。
90 |
91 | ISP处理流程类ISPpipeline主要是进行isp算法的处理。他会使用界面配置的参数RawImageParams,调用ISP后台处理线程进行处理。期间产生的图像,用RawImageInfo来存储。ISP后台处理线程依赖ispfunction中的算法实现,因此如果想要拓展算法,只需要修改isp.py中的实现即可。
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/tools/rawimageeditor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/tools/rawimageeditor/__init__.py
--------------------------------------------------------------------------------
/tools/rawimageeditor/ispfunction.py:
--------------------------------------------------------------------------------
1 | import tools.rawimageeditor.isp as isp
2 | import tools.rawimageeditor.debayer as debayer
3 |
4 | # pipeline名称全部小写
5 | pipeline_dict = {
6 | "original raw": isp.get_src_raw_data,
7 | "black level": isp.black_level_correction,
8 | "digital gain": isp.apply_digital_gain,
9 | "blc": isp.black_level_correction,
10 | "rolloff": isp.rolloff_correction,
11 | "bad pixel correction": isp.bad_pixel_correction,
12 | "bayer denoise": None,
13 | "demosaic": debayer.demosaic,
14 | "awb": isp.channel_gain_white_balance,
15 | "ccm": isp.color_correction,
16 | "gamma": isp.gamma_correction,
17 | "ltm": isp.ltm_correction,
18 | "csc": isp.color_space_conversion,
19 | "yuv denoise": isp.wavelet_denoise,
20 | "yuv sharpen": isp.sharpen
21 | }
--------------------------------------------------------------------------------
/tools/rawimageeditor/isppipeline.py:
--------------------------------------------------------------------------------
1 | from tools.rawimageeditor.RawImageParams import RawImageParams
2 | from tools.rawimageeditor.RawImageInfo import RawImageInfo
3 | import tools.rawimageeditor.ispfunction as ispfunc
4 | from imp import reload
5 | import time
6 | from PySide2.QtCore import Signal, QThread
7 | from PySide2.QtWidgets import QMessageBox
8 | from threading import Lock
9 |
10 | class IspPipeline():
11 | def __init__(self, parmas, process_bar=None):
12 | self.old_pipeline = []
13 | self.pipeline = []
14 | self.params = parmas
15 | # img_list存储了pipeline中途所有的图像
16 | # img_list长度比pipeline长1
17 | self.img_list = [RawImageInfo()]
18 | self.process_bar = process_bar
19 | self.imglist_mutex = Lock()
20 | self.ispProcthread = ISPProc(self.params, self.img_list, self.imglist_mutex)
21 |
22 | def reload_isp(self):
23 | """
24 | func: 热更新 重载ISP算法模块
25 | """
26 | reload(ispfunc.debayer)
27 | reload(ispfunc.isp)
28 | reload(ispfunc)
29 | self.params.need_flush = True
30 | if (self.process_bar is not None):
31 | self.process_bar.setValue(0)
32 |
33 | def set_pipeline(self, pipeline):
34 | self.old_pipeline = self.pipeline
35 | self.pipeline = pipeline
36 |
37 | def pipeline_clear(self):
38 | self.old_pipeline = self.pipeline
39 | self.pipeline = []
40 |
41 | def pipeline_reset(self):
42 | """
43 | func: 重新开始一个pipeline,把以前的图像清除
44 | """
45 | if(len(self.img_list) > 1):
46 | self.imglist_mutex.acquire()
47 | self.img_list = [RawImageInfo()]
48 | self.imglist_mutex.release()
49 | self.old_pipeline = []
50 | self.pipeline = []
51 | return True
52 | return False
53 |
54 | def add_pipeline_node(self, node):
55 | """
56 | func: 为pipeline添加一个节点
57 | 输入是pipeline_dict的字符串
58 | """
59 | if(node.lower() in ispfunc.pipeline_dict):
60 | self.pipeline.append(node.lower())
61 |
62 | def get_pipeline_node_index(self, node):
63 | """
64 | func: 返回该node在pipeline的index, 如果不存在,就返回-1
65 | """
66 | if(node.lower() in ispfunc.pipeline_dict and node.lower() in self.pipeline):
67 | return self.pipeline.index(node.lower())
68 | else:
69 | return -1
70 |
71 | def compare_pipeline(self):
72 | """
73 | func: 对比新老pipeline的区别
74 | 如果不同的话,会返回一个index,表示从第index个值开始不一样的,注意这个index可能不存在于老的pipeline中
75 | 如果相同的话,会返回0
76 | """
77 | for i, node in enumerate(self.pipeline):
78 | if(i > len(self.old_pipeline) - 1 or node != self.old_pipeline[i]):
79 | return i
80 | return -1
81 |
82 | def check_pipeline(self):
83 | """
84 | func: 检查pipeline,如果有不同的,修改img_list
85 | ret: 如果pipeline不需要修改,就返回None,如果需要修改,就返回需要修改的pipeline
86 | """
87 | # 如果参数有修改,优先返回需要修改的pipeline
88 | if(self.params.need_flush == True):
89 | if(len(self.params.need_flush_isp) > 0):
90 | index = -1
91 | self.params.need_flush = False
92 | for node in self.params.need_flush_isp:
93 | index = self.get_pipeline_node_index(node)
94 | if(index != -1):
95 | self.remove_img_node_tail(index)
96 | # 需要把新老pipeline进行对比
97 | index_ret = self.compare_pipeline()
98 | if(index_ret != -1):
99 | min_index = min(index_ret, index)
100 | self.remove_img_node_tail(min_index)
101 | return self.pipeline[min_index:]
102 | else:
103 | return self.pipeline[index:]
104 | else:
105 | return None
106 | else:
107 | self.remove_img_node_tail(0)
108 | self.params.need_flush = False
109 | return self.pipeline
110 | else:
111 | index = self.compare_pipeline()
112 | if(index != -1):
113 | self.remove_img_node_tail(index)
114 | return self.pipeline[index:]
115 | return None
116 |
117 | def run_pipeline(self):
118 | """
119 | func: 运行pipeline,process_bar是用于显示进度的process bar, callback是运行完的回调函数
120 | """
121 | pipeline = self.check_pipeline()
122 | print(pipeline)
123 | self.ispProcthread.set_pipeline(pipeline)
124 | self.ispProcthread.start()
125 |
126 | def remove_img_node_tail(self, index):
127 | """
128 | func: 去除>=index之后的node,由于image的长度比pipeline多1,因此需要将index+1
129 | """
130 | index += 1
131 | self.imglist_mutex.acquire()
132 | while index < len(self.img_list):
133 | self.img_list.pop()
134 | self.imglist_mutex.release()
135 |
136 | def get_image(self, index):
137 | """
138 | func: 获取pipeline中的一幅图像
139 | 如果输入-1,则返回最后一幅图像
140 | """
141 | ret_img = None
142 | self.imglist_mutex.acquire()
143 | if (index < len(self.img_list) and index >= 0):
144 | ret_img = self.img_list[index]
145 | elif (index < 0 and len(self.pipeline)+1 + index < len(self.img_list)):
146 | ret_img = self.img_list[len(self.pipeline)+1 + index]
147 | self.imglist_mutex.release()
148 | if(ret_img is not None):
149 | return ret_img
150 | else:
151 | return RawImageInfo()
152 |
153 | class ISPProc(QThread):
154 | doneCB = Signal() # 自定义信号,其中 object 为信号承载数据的类型
155 | processRateCB = Signal(int)
156 | costTimeCB = Signal(str)
157 | errorCB = Signal(str)
158 |
159 | def __init__(self, params, img_list, mutex:Lock, parent=None):
160 | super(ISPProc, self).__init__(parent)
161 | self.params = params
162 | self.img_list = img_list
163 | self.pipeline = None
164 | self.mutex = mutex
165 |
166 | def run_node(self, node, data):
167 | # 这里进行检查之后,后续就不需要检查了
168 | if(data is not None and self.params is not None):
169 | return ispfunc.pipeline_dict[node](data, self.params)
170 | elif(self.params is None):
171 | self.params.set_error_str("输入的参数为空")
172 | return None
173 |
174 | def set_pipeline(self, pipeline):
175 | self.pipeline = pipeline
176 |
177 | def run(self):
178 | self.processRateCB.emit(0)
179 | if (self.pipeline is not None):
180 | length = len(self.pipeline)
181 | i = 1
182 | params = self.params
183 | start_time = time.time()
184 | for node in self.pipeline:
185 | data = self.img_list[-1]
186 | try:
187 | ret_img = self.run_node(node, data)
188 | except Exception as e:
189 | self.errorCB.emit("ISP算法[{}]运行错误:{}\r\n{}".format(node, params.get_error_str(),e))
190 | return
191 |
192 | if(ret_img is not None):
193 | self.mutex.acquire()
194 | self.img_list.append(ret_img)
195 | self.mutex.release()
196 | else:
197 | self.errorCB.emit(params.get_error_str())
198 | break
199 | self.processRateCB.emit(i / length * 100)
200 | i += 1
201 | stop_time = time.time()
202 | self.costTimeCB.emit('总耗时:{:.3f}s'.format(stop_time-start_time))
203 | self.doneCB.emit()
204 | else:
205 | self.processRateCB.emit(100)
--------------------------------------------------------------------------------
/tools/rawimageeditor/ui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/tools/rawimageeditor/ui/__init__.py
--------------------------------------------------------------------------------
/tools/shake_test/Readme.md:
--------------------------------------------------------------------------------
1 | # 防抖检测工具
2 | ## 使用方法
3 | 1. 拍摄视频,视频的要求:尽量用棋盘格等边缘比较明显的物体,图像中心尽量有明显的特征点
4 | 2. 导入振动台上拍摄的视频,可以通过打开文件或者拖动文件到视频预览窗口进行导入
5 | 3. 点击右下角的开始,会开始对图像进行处理,并会更新实时的结果。点击停止,会停止图像的处理
6 | 4. 视频预览会显示每个特征点的运动轨迹,中心的特征点会着重标注出来。确保每个点的轨迹都没有问题。在最终结果那一栏会得出这段时间内的最终结果
7 |
8 | ## 高级操作
9 | 1. ROI上下边界:由于有些视频存在OSD,会干扰到特征点的选取,可以调整上下边界控制特征点的选取范围
10 | 2. 跳过帧数:因为有的时候前几帧有干扰的物体,或者效果不佳,可以跳过前几帧,再进行特征点选取。
11 | 3. 特征点的数量:如果画面中心没有特征点,那么可以调整此滑块。如果特征点过多,也可以调整此滑块
12 | 4. 特征点的运动幅度:如果运动幅度过大,出现跟踪错误的话,就增大特征点的运动幅度
13 | 5. 计算间隔(帧):每多少帧会输出一个实时的结果,确保每次的实时结果都比较正常,再去看最终结果
14 | 6. 计算方向:跟最后结果挂钩,只显示某个方向上的结果
15 | 7. 去除静止的特征点:可以排除roi等静止的点,优先选用ROI的方式
16 |
17 | ## 显示情况
18 | 1. 视频预览中会把所有的特征点着重标出来,中心参与计算的特征点更加明显
19 |
20 |
21 | ## 评价标准
22 |
23 | 我觉得应该有两个,一个是图片中心的位移,一个是图像的形变程度
24 | 如果图像不变形,每个特征点相对于图片中心都是固定的,
25 | 每个特征点的位移应该是和图片中心一致的
26 | 计算方法:第一帧算出每个特征点相对于图片中心的距离,后续每帧的特征点
27 | 都与第一帧的距离做一个差,所有差的平方和就作为图片的形变程度
28 |
29 | 每个点的运动角度
30 |
31 | 形变程度和每个点的运动方向和运动速度有关,而两帧之间的距离太小,计算容易出现误差,直接拿最左边和最右边的值做一个差。
32 | 每个点的运动可以看做一个向量,每个点的运动向量和中心点的运动向量差就是图像的变形程度
33 |
34 | ## 原理
35 | 先在第一张图像上找到比较好的特征点
36 | 后续的帧对这些特征点进行跟踪
37 |
38 | ## 遇到的问题
39 | 有些特征点跟踪的时候会产生漂移,甚至跟丢了的情况
40 | 1. 利用cornerSubPix计算出亚像素点,更加精准
41 | 2. 还可能是光流法搜索的区域不够大,需要增大winSize
--------------------------------------------------------------------------------
/tools/shake_test/rtspconfigview.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'rtspconfigview.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.0
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
12 | QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
13 | from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
14 | QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
15 | QPixmap, QRadialGradient)
16 | from PySide2.QtWidgets import *
17 |
18 |
19 | class Ui_RtspConfigView(object):
20 | def setupUi(self, RtspConfigView):
21 | if not RtspConfigView.objectName():
22 | RtspConfigView.setObjectName(u"RtspConfigView")
23 | RtspConfigView.resize(174, 186)
24 | self.gridLayout = QGridLayout(RtspConfigView)
25 | self.gridLayout.setObjectName(u"gridLayout")
26 | self.buttonBox = QDialogButtonBox(RtspConfigView)
27 | self.buttonBox.setObjectName(u"buttonBox")
28 | self.buttonBox.setOrientation(Qt.Horizontal)
29 | self.buttonBox.setStandardButtons(QDialogButtonBox.Ok)
30 |
31 | self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1)
32 |
33 | self.gridLayout_2 = QGridLayout()
34 | self.gridLayout_2.setObjectName(u"gridLayout_2")
35 | self.username = QLineEdit(RtspConfigView)
36 | self.username.setObjectName(u"username")
37 |
38 | self.gridLayout_2.addWidget(self.username, 0, 1, 1, 1)
39 |
40 | self.label_2 = QLabel(RtspConfigView)
41 | self.label_2.setObjectName(u"label_2")
42 |
43 | self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1)
44 |
45 | self.label_3 = QLabel(RtspConfigView)
46 | self.label_3.setObjectName(u"label_3")
47 |
48 | self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1)
49 |
50 | self.port = QLineEdit(RtspConfigView)
51 | self.port.setObjectName(u"port")
52 |
53 | self.gridLayout_2.addWidget(self.port, 3, 1, 1, 1)
54 |
55 | self.ip = QLineEdit(RtspConfigView)
56 | self.ip.setObjectName(u"ip")
57 |
58 | self.gridLayout_2.addWidget(self.ip, 2, 1, 1, 1)
59 |
60 | self.label_4 = QLabel(RtspConfigView)
61 | self.label_4.setObjectName(u"label_4")
62 |
63 | self.gridLayout_2.addWidget(self.label_4, 3, 0, 1, 1)
64 |
65 | self.password = QLineEdit(RtspConfigView)
66 | self.password.setObjectName(u"password")
67 |
68 | self.gridLayout_2.addWidget(self.password, 1, 1, 1, 1)
69 |
70 | self.label = QLabel(RtspConfigView)
71 | self.label.setObjectName(u"label")
72 |
73 | self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
74 |
75 | self.isphoto = QCheckBox(RtspConfigView)
76 | self.isphoto.setObjectName(u"isphoto")
77 | self.isphoto.setChecked(True)
78 |
79 | self.gridLayout_2.addWidget(self.isphoto, 4, 1, 1, 1)
80 |
81 |
82 | self.gridLayout.addLayout(self.gridLayout_2, 0, 0, 1, 1)
83 |
84 |
85 | self.retranslateUi(RtspConfigView)
86 | self.buttonBox.accepted.connect(RtspConfigView.accept)
87 | self.buttonBox.rejected.connect(RtspConfigView.reject)
88 |
89 | QMetaObject.connectSlotsByName(RtspConfigView)
90 | # setupUi
91 |
92 | def retranslateUi(self, RtspConfigView):
93 | RtspConfigView.setWindowTitle(QCoreApplication.translate("RtspConfigView", u"\u6253\u5f00\u8bbe\u5907(RTSP)", None))
94 | self.username.setText(QCoreApplication.translate("RtspConfigView", u"admin", None))
95 | self.label_2.setText(QCoreApplication.translate("RtspConfigView", u"\u5bc6\u7801", None))
96 | self.label_3.setText(QCoreApplication.translate("RtspConfigView", u"IP", None))
97 | self.port.setText(QCoreApplication.translate("RtspConfigView", u"1554", None))
98 | self.ip.setText(QCoreApplication.translate("RtspConfigView", u"127.0.0.1", None))
99 | self.label_4.setText(QCoreApplication.translate("RtspConfigView", u"\u7aef\u53e3", None))
100 | self.password.setText(QCoreApplication.translate("RtspConfigView", u"admin123", None))
101 | self.label.setText(QCoreApplication.translate("RtspConfigView", u"\u7528\u6237\u540d", None))
102 | self.isphoto.setText(QCoreApplication.translate("RtspConfigView", u"\u79fb\u52a8\u8bbe\u5907", None))
103 | # retranslateUi
104 |
105 |
--------------------------------------------------------------------------------
/tools/shake_test/rtspconfigview.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | RtspConfigView
4 |
5 |
6 |
7 | 0
8 | 0
9 | 174
10 | 186
11 |
12 |
13 |
14 | 打开设备(RTSP)
15 |
16 |
17 | -
18 |
19 |
20 | Qt::Horizontal
21 |
22 |
23 | QDialogButtonBox::Ok
24 |
25 |
26 |
27 | -
28 |
29 |
-
30 |
31 |
32 | admin
33 |
34 |
35 |
36 | -
37 |
38 |
39 | 密码
40 |
41 |
42 |
43 | -
44 |
45 |
46 | IP
47 |
48 |
49 |
50 | -
51 |
52 |
53 | 1554
54 |
55 |
56 |
57 | -
58 |
59 |
60 | 127.0.0.1
61 |
62 |
63 |
64 | -
65 |
66 |
67 | 端口
68 |
69 |
70 |
71 | -
72 |
73 |
74 | admin123
75 |
76 |
77 |
78 | -
79 |
80 |
81 | 用户名
82 |
83 |
84 |
85 | -
86 |
87 |
88 | 移动设备
89 |
90 |
91 | true
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | buttonBox
103 | accepted()
104 | RtspConfigView
105 | accept()
106 |
107 |
108 | 248
109 | 254
110 |
111 |
112 | 157
113 | 274
114 |
115 |
116 |
117 |
118 | buttonBox
119 | rejected()
120 | RtspConfigView
121 | reject()
122 |
123 |
124 | 316
125 | 260
126 |
127 |
128 | 286
129 | 274
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/tools/video_compare/Readme.md:
--------------------------------------------------------------------------------
1 | # 视频对比工具
2 | ## 使用方法
3 | 1. 拍摄视频,视频的要求:尽量用棋盘格等边缘比较明显的物体,图像中心尽量有明显的特征点
4 | 2. 导入振动台上拍摄的视频,可以通过打开文件或者拖动文件到视频预览窗口进行导入
5 | 3. 点击右下角的开始,会开始对图像进行处理,并会更新实时的结果。点击停止,会停止图像的处理
6 | 4. 视频预览会显示每个特征点的运动轨迹,中心的特征点会着重标注出来。确保每个点的轨迹都没有问题。在最终结果那一栏会得出这段时间内的最终结果
7 |
8 | ## 高级操作
9 | 1. ROI上下边界:由于有些视频存在OSD,会干扰到特征点的选取,可以调整上下边界控制特征点的选取范围
10 | 2. 跳过帧数:因为有的时候前几帧有干扰的物体,或者效果不佳,可以跳过前几帧,再进行特征点选取。
11 | 3. 特征点的数量:如果画面中心没有特征点,那么可以调整此滑块。如果特征点过多,也可以调整此滑块
12 | 4. 特征点的运动幅度:如果运动幅度过大,出现跟踪错误的话,就增大特征点的运动幅度
13 | 5. 计算间隔(帧):每多少帧会输出一个实时的结果,确保每次的实时结果都比较正常,再去看最终结果
14 | 6. 计算方向:跟最后结果挂钩,只显示某个方向上的结果
15 | 7. 去除静止的特征点:可以排除roi等静止的点,优先选用ROI的方式
16 |
17 | ## 显示情况
18 | 1. 视频预览中会把所有的特征点着重标出来,中心参与计算的特征点更加明显
19 |
20 |
21 | ## 评价标准
22 |
23 | 我觉得应该有两个,一个是图片中心的位移,一个是图像的形变程度
24 | 如果图像不变形,每个特征点相对于图片中心都是固定的,
25 | 每个特征点的位移应该是和图片中心一致的
26 | 计算方法:第一帧算出每个特征点相对于图片中心的距离,后续每帧的特征点
27 | 都与第一帧的距离做一个差,所有差的平方和就作为图片的形变程度
28 |
29 | 每个点的运动角度
30 |
31 | 形变程度和每个点的运动方向和运动速度有关,而两帧之间的距离太小,计算容易出现误差,直接拿最左边和最右边的值做一个差。
32 | 每个点的运动可以看做一个向量,每个点的运动向量和中心点的运动向量差就是图像的变形程度
33 |
34 | ## 原理
35 | 先在第一张图像上找到比较好的特征点
36 | 后续的帧对这些特征点进行跟踪
37 |
38 | ## 遇到的问题
39 | 有些特征点跟踪的时候会产生漂移,甚至跟丢了的情况
40 | 1. 利用cornerSubPix计算出亚像素点,更加精准
41 | 2. 还可能是光流法搜索的区域不够大,需要增大winSize
--------------------------------------------------------------------------------
/tools/video_compare/rtspconfigview.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'rtspconfigview.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.0
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
12 | QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
13 | from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
14 | QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
15 | QPixmap, QRadialGradient)
16 | from PySide2.QtWidgets import *
17 |
18 |
19 | class Ui_RtspConfigView(object):
20 | def setupUi(self, RtspConfigView):
21 | if not RtspConfigView.objectName():
22 | RtspConfigView.setObjectName(u"RtspConfigView")
23 | RtspConfigView.resize(174, 186)
24 | self.gridLayout = QGridLayout(RtspConfigView)
25 | self.gridLayout.setObjectName(u"gridLayout")
26 | self.buttonBox = QDialogButtonBox(RtspConfigView)
27 | self.buttonBox.setObjectName(u"buttonBox")
28 | self.buttonBox.setOrientation(Qt.Horizontal)
29 | self.buttonBox.setStandardButtons(QDialogButtonBox.Ok)
30 |
31 | self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1)
32 |
33 | self.gridLayout_2 = QGridLayout()
34 | self.gridLayout_2.setObjectName(u"gridLayout_2")
35 | self.username = QLineEdit(RtspConfigView)
36 | self.username.setObjectName(u"username")
37 |
38 | self.gridLayout_2.addWidget(self.username, 0, 1, 1, 1)
39 |
40 | self.label_2 = QLabel(RtspConfigView)
41 | self.label_2.setObjectName(u"label_2")
42 |
43 | self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1)
44 |
45 | self.label_3 = QLabel(RtspConfigView)
46 | self.label_3.setObjectName(u"label_3")
47 |
48 | self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1)
49 |
50 | self.port = QLineEdit(RtspConfigView)
51 | self.port.setObjectName(u"port")
52 |
53 | self.gridLayout_2.addWidget(self.port, 3, 1, 1, 1)
54 |
55 | self.ip = QLineEdit(RtspConfigView)
56 | self.ip.setObjectName(u"ip")
57 |
58 | self.gridLayout_2.addWidget(self.ip, 2, 1, 1, 1)
59 |
60 | self.label_4 = QLabel(RtspConfigView)
61 | self.label_4.setObjectName(u"label_4")
62 |
63 | self.gridLayout_2.addWidget(self.label_4, 3, 0, 1, 1)
64 |
65 | self.password = QLineEdit(RtspConfigView)
66 | self.password.setObjectName(u"password")
67 |
68 | self.gridLayout_2.addWidget(self.password, 1, 1, 1, 1)
69 |
70 | self.label = QLabel(RtspConfigView)
71 | self.label.setObjectName(u"label")
72 |
73 | self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
74 |
75 | self.isphoto = QCheckBox(RtspConfigView)
76 | self.isphoto.setObjectName(u"isphoto")
77 | self.isphoto.setChecked(True)
78 |
79 | self.gridLayout_2.addWidget(self.isphoto, 4, 1, 1, 1)
80 |
81 |
82 | self.gridLayout.addLayout(self.gridLayout_2, 0, 0, 1, 1)
83 |
84 |
85 | self.retranslateUi(RtspConfigView)
86 | self.buttonBox.accepted.connect(RtspConfigView.accept)
87 | self.buttonBox.rejected.connect(RtspConfigView.reject)
88 |
89 | QMetaObject.connectSlotsByName(RtspConfigView)
90 | # setupUi
91 |
92 | def retranslateUi(self, RtspConfigView):
93 | RtspConfigView.setWindowTitle(QCoreApplication.translate("RtspConfigView", u"\u6253\u5f00\u8bbe\u5907(RTSP)", None))
94 | self.username.setText(QCoreApplication.translate("RtspConfigView", u"admin", None))
95 | self.label_2.setText(QCoreApplication.translate("RtspConfigView", u"\u5bc6\u7801", None))
96 | self.label_3.setText(QCoreApplication.translate("RtspConfigView", u"IP", None))
97 | self.port.setText(QCoreApplication.translate("RtspConfigView", u"1554", None))
98 | self.ip.setText(QCoreApplication.translate("RtspConfigView", u"127.0.0.1", None))
99 | self.label_4.setText(QCoreApplication.translate("RtspConfigView", u"\u7aef\u53e3", None))
100 | self.password.setText(QCoreApplication.translate("RtspConfigView", u"admin123", None))
101 | self.label.setText(QCoreApplication.translate("RtspConfigView", u"\u7528\u6237\u540d", None))
102 | self.isphoto.setText(QCoreApplication.translate("RtspConfigView", u"\u79fb\u52a8\u8bbe\u5907", None))
103 | # retranslateUi
104 |
105 |
--------------------------------------------------------------------------------
/tools/video_compare/rtspconfigview.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | RtspConfigView
4 |
5 |
6 |
7 | 0
8 | 0
9 | 174
10 | 186
11 |
12 |
13 |
14 | 打开设备(RTSP)
15 |
16 |
17 | -
18 |
19 |
20 | Qt::Horizontal
21 |
22 |
23 | QDialogButtonBox::Ok
24 |
25 |
26 |
27 | -
28 |
29 |
-
30 |
31 |
32 | admin
33 |
34 |
35 |
36 | -
37 |
38 |
39 | 密码
40 |
41 |
42 |
43 | -
44 |
45 |
46 | IP
47 |
48 |
49 |
50 | -
51 |
52 |
53 | 1554
54 |
55 |
56 |
57 | -
58 |
59 |
60 | 127.0.0.1
61 |
62 |
63 |
64 | -
65 |
66 |
67 | 端口
68 |
69 |
70 |
71 | -
72 |
73 |
74 | admin123
75 |
76 |
77 |
78 | -
79 |
80 |
81 | 用户名
82 |
83 |
84 |
85 | -
86 |
87 |
88 | 移动设备
89 |
90 |
91 | true
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | buttonBox
103 | accepted()
104 | RtspConfigView
105 | accept()
106 |
107 |
108 | 248
109 | 254
110 |
111 |
112 | 157
113 | 274
114 |
115 |
116 |
117 |
118 | buttonBox
119 | rejected()
120 | RtspConfigView
121 | reject()
122 |
123 |
124 | 316
125 | 260
126 |
127 |
128 | 286
129 | 274
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/tools/video_compare/shake_test_window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'shake_test_window.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.0
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
12 | QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
13 | from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
14 | QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
15 | QPixmap, QRadialGradient)
16 | from PySide2.QtWidgets import *
17 |
18 | import resource_rc
19 |
20 | class Ui_ShakeTestWindow(object):
21 | def setupUi(self, ShakeTestWindow):
22 | if not ShakeTestWindow.objectName():
23 | ShakeTestWindow.setObjectName(u"ShakeTestWindow")
24 | ShakeTestWindow.resize(1034, 653)
25 | self.actionstart = QAction(ShakeTestWindow)
26 | self.actionstart.setObjectName(u"actionstart")
27 | icon = QIcon()
28 | icon.addFile(u":/tool_icon/resource/start.png", QSize(), QIcon.Normal, QIcon.Off)
29 | self.actionstart.setIcon(icon)
30 | self.actionpause = QAction(ShakeTestWindow)
31 | self.actionpause.setObjectName(u"actionpause")
32 | icon1 = QIcon()
33 | icon1.addFile(u":/tool_icon/resource/pause.png", QSize(), QIcon.Normal, QIcon.Off)
34 | self.actionpause.setIcon(icon1)
35 | self.actionrestart = QAction(ShakeTestWindow)
36 | self.actionrestart.setObjectName(u"actionrestart")
37 | icon2 = QIcon()
38 | icon2.addFile(u":/tool_icon/resource/restart .png", QSize(), QIcon.Normal, QIcon.Off)
39 | self.actionrestart.setIcon(icon2)
40 | self.actionspeedup = QAction(ShakeTestWindow)
41 | self.actionspeedup.setObjectName(u"actionspeedup")
42 | icon3 = QIcon()
43 | icon3.addFile(u":/tool_icon/resource/\u5feb\u8fdb.png", QSize(), QIcon.Normal, QIcon.Off)
44 | self.actionspeedup.setIcon(icon3)
45 | self.actionspeeddown = QAction(ShakeTestWindow)
46 | self.actionspeeddown.setObjectName(u"actionspeeddown")
47 | icon4 = QIcon()
48 | icon4.addFile(u":/tool_icon/resource/\u5feb\u9000.png", QSize(), QIcon.Normal, QIcon.Off)
49 | self.actionspeeddown.setIcon(icon4)
50 | self.actionadd = QAction(ShakeTestWindow)
51 | self.actionadd.setObjectName(u"actionadd")
52 | icon5 = QIcon()
53 | icon5.addFile(u":/tool_icon/resource/add.png", QSize(), QIcon.Normal, QIcon.Off)
54 | self.actionadd.setIcon(icon5)
55 | self.actionsubtract = QAction(ShakeTestWindow)
56 | self.actionsubtract.setObjectName(u"actionsubtract")
57 | icon6 = QIcon()
58 | icon6.addFile(u":/tool_icon/resource/subtract.png", QSize(), QIcon.Normal, QIcon.Off)
59 | self.actionsubtract.setIcon(icon6)
60 | self.centralwidget = QWidget(ShakeTestWindow)
61 | self.centralwidget.setObjectName(u"centralwidget")
62 | self.gridLayout = QGridLayout(self.centralwidget)
63 | self.gridLayout.setObjectName(u"gridLayout")
64 | self.horizontalLayout = QHBoxLayout()
65 | self.horizontalLayout.setObjectName(u"horizontalLayout")
66 |
67 | self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
68 |
69 | ShakeTestWindow.setCentralWidget(self.centralwidget)
70 | self.menubar = QMenuBar(ShakeTestWindow)
71 | self.menubar.setObjectName(u"menubar")
72 | self.menubar.setGeometry(QRect(0, 0, 1034, 22))
73 | ShakeTestWindow.setMenuBar(self.menubar)
74 | self.statusbar = QStatusBar(ShakeTestWindow)
75 | self.statusbar.setObjectName(u"statusbar")
76 | ShakeTestWindow.setStatusBar(self.statusbar)
77 | self.toolBar = QToolBar(ShakeTestWindow)
78 | self.toolBar.setObjectName(u"toolBar")
79 | ShakeTestWindow.addToolBar(Qt.RightToolBarArea, self.toolBar)
80 |
81 | self.toolBar.addAction(self.actionstart)
82 | self.toolBar.addAction(self.actionpause)
83 | self.toolBar.addAction(self.actionrestart)
84 | self.toolBar.addSeparator()
85 | self.toolBar.addAction(self.actionadd)
86 | self.toolBar.addAction(self.actionsubtract)
87 | self.toolBar.addSeparator()
88 | self.toolBar.addAction(self.actionspeedup)
89 | self.toolBar.addAction(self.actionspeeddown)
90 |
91 | self.retranslateUi(ShakeTestWindow)
92 |
93 | QMetaObject.connectSlotsByName(ShakeTestWindow)
94 | # setupUi
95 |
96 | def retranslateUi(self, ShakeTestWindow):
97 | ShakeTestWindow.setWindowTitle(QCoreApplication.translate("ShakeTestWindow", u"\u89c6\u9891\u5bf9\u6bd4\u5de5\u5177", None))
98 | self.actionstart.setText(QCoreApplication.translate("ShakeTestWindow", u"start", None))
99 | #if QT_CONFIG(tooltip)
100 | self.actionstart.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u5f00\u59cb", None))
101 | #endif // QT_CONFIG(tooltip)
102 | self.actionpause.setText(QCoreApplication.translate("ShakeTestWindow", u"pause", None))
103 | #if QT_CONFIG(tooltip)
104 | self.actionpause.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u6682\u505c", None))
105 | #endif // QT_CONFIG(tooltip)
106 | self.actionrestart.setText(QCoreApplication.translate("ShakeTestWindow", u"restart", None))
107 | #if QT_CONFIG(tooltip)
108 | self.actionrestart.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u91cd\u65b0\u5f00\u59cb", None))
109 | #endif // QT_CONFIG(tooltip)
110 | self.actionspeedup.setText(QCoreApplication.translate("ShakeTestWindow", u"speedup", None))
111 | #if QT_CONFIG(tooltip)
112 | self.actionspeedup.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u52a0\u901f", None))
113 | #endif // QT_CONFIG(tooltip)
114 | self.actionspeeddown.setText(QCoreApplication.translate("ShakeTestWindow", u"speeddown", None))
115 | #if QT_CONFIG(tooltip)
116 | self.actionspeeddown.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u51cf\u901f", None))
117 | #endif // QT_CONFIG(tooltip)
118 | self.actionadd.setText(QCoreApplication.translate("ShakeTestWindow", u"add", None))
119 | #if QT_CONFIG(tooltip)
120 | self.actionadd.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u589e\u52a0\u89c6\u9891", None))
121 | #endif // QT_CONFIG(tooltip)
122 | self.actionsubtract.setText(QCoreApplication.translate("ShakeTestWindow", u"subtract", None))
123 | #if QT_CONFIG(tooltip)
124 | self.actionsubtract.setToolTip(QCoreApplication.translate("ShakeTestWindow", u"\u51cf\u5c11\u89c6\u9891", None))
125 | #endif // QT_CONFIG(tooltip)
126 | self.toolBar.setWindowTitle(QCoreApplication.translate("ShakeTestWindow", u"toolBar", None))
127 | # retranslateUi
128 |
129 |
--------------------------------------------------------------------------------
/tools/video_compare/shake_test_window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | ShakeTestWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1034
10 | 653
11 |
12 |
13 |
14 | 视频对比工具
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
22 |
23 |
33 |
34 |
35 |
36 | toolBar
37 |
38 |
39 | RightToolBarArea
40 |
41 |
42 | false
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | :/tool_icon/resource/start.png:/tool_icon/resource/start.png
58 |
59 |
60 | start
61 |
62 |
63 | 开始
64 |
65 |
66 |
67 |
68 |
69 | :/tool_icon/resource/pause.png:/tool_icon/resource/pause.png
70 |
71 |
72 | pause
73 |
74 |
75 | 暂停
76 |
77 |
78 |
79 |
80 |
81 | :/tool_icon/resource/restart .png:/tool_icon/resource/restart .png
82 |
83 |
84 | restart
85 |
86 |
87 | 重新开始
88 |
89 |
90 |
91 |
92 |
93 | :/tool_icon/resource/快进.png:/tool_icon/resource/快进.png
94 |
95 |
96 | speedup
97 |
98 |
99 | 加速
100 |
101 |
102 |
103 |
104 |
105 | :/tool_icon/resource/快退.png:/tool_icon/resource/快退.png
106 |
107 |
108 | speeddown
109 |
110 |
111 | 减速
112 |
113 |
114 |
115 |
116 |
117 | :/tool_icon/resource/add.png:/tool_icon/resource/add.png
118 |
119 |
120 | add
121 |
122 |
123 | 增加视频
124 |
125 |
126 |
127 |
128 |
129 | :/tool_icon/resource/subtract.png:/tool_icon/resource/subtract.png
130 |
131 |
132 | subtract
133 |
134 |
135 | 减少视频
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/tools/video_compare/video_pre_settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'video_pre_settings.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.0
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import (QCoreApplication, QDate, QDateTime, QMetaObject,
12 | QObject, QPoint, QRect, QSize, QTime, QUrl, Qt)
13 | from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
14 | QFontDatabase, QIcon, QKeySequence, QLinearGradient, QPalette, QPainter,
15 | QPixmap, QRadialGradient)
16 | from PySide2.QtWidgets import *
17 |
18 |
19 | class Ui_video_pre_settings(object):
20 | def setupUi(self, video_pre_settings):
21 | if not video_pre_settings.objectName():
22 | video_pre_settings.setObjectName(u"video_pre_settings")
23 | video_pre_settings.resize(310, 220)
24 | video_pre_settings.setMaximumSize(QSize(16777215, 16777215))
25 | self.gridLayout_2 = QGridLayout(video_pre_settings)
26 | self.gridLayout_2.setObjectName(u"gridLayout_2")
27 | self.verticalLayout = QVBoxLayout()
28 | self.verticalLayout.setObjectName(u"verticalLayout")
29 | self.groupBox = QGroupBox(video_pre_settings)
30 | self.groupBox.setObjectName(u"groupBox")
31 | self.gridLayout_3 = QGridLayout(self.groupBox)
32 | self.gridLayout_3.setObjectName(u"gridLayout_3")
33 | self.videoview = QHBoxLayout()
34 | self.videoview.setObjectName(u"videoview")
35 |
36 | self.gridLayout_3.addLayout(self.videoview, 0, 0, 1, 1)
37 |
38 |
39 | self.verticalLayout.addWidget(self.groupBox)
40 |
41 | self.groupBox_2 = QGroupBox(video_pre_settings)
42 | self.groupBox_2.setObjectName(u"groupBox_2")
43 | self.groupBox_2.setMaximumSize(QSize(16777215, 113))
44 | self.gridLayout_4 = QGridLayout(self.groupBox_2)
45 | self.gridLayout_4.setObjectName(u"gridLayout_4")
46 | self.verticalLayout_2 = QVBoxLayout()
47 | self.verticalLayout_2.setObjectName(u"verticalLayout_2")
48 | self.gridLayout = QGridLayout()
49 | self.gridLayout.setObjectName(u"gridLayout")
50 | self.label = QLabel(self.groupBox_2)
51 | self.label.setObjectName(u"label")
52 | self.label.setMaximumSize(QSize(16777215, 16777215))
53 |
54 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
55 |
56 | self.path = QLineEdit(self.groupBox_2)
57 | self.path.setObjectName(u"path")
58 | self.path.setMaximumSize(QSize(16777215, 16777215))
59 |
60 | self.gridLayout.addWidget(self.path, 0, 1, 1, 1)
61 |
62 | self.label_2 = QLabel(self.groupBox_2)
63 | self.label_2.setObjectName(u"label_2")
64 | self.label_2.setMaximumSize(QSize(16777215, 16777215))
65 |
66 | self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
67 |
68 | self.skipframe = QSpinBox(self.groupBox_2)
69 | self.skipframe.setObjectName(u"skipframe")
70 | self.skipframe.setMaximumSize(QSize(16777215, 16777215))
71 | self.skipframe.setMaximum(10000)
72 | self.skipframe.setSingleStep(10)
73 |
74 | self.gridLayout.addWidget(self.skipframe, 1, 1, 1, 1)
75 |
76 |
77 | self.verticalLayout_2.addLayout(self.gridLayout)
78 |
79 | self.horizontalLayout = QHBoxLayout()
80 | self.horizontalLayout.setObjectName(u"horizontalLayout")
81 | self.open_rtsp = QPushButton(self.groupBox_2)
82 | self.open_rtsp.setObjectName(u"open_rtsp")
83 | self.open_rtsp.setMaximumSize(QSize(16777215, 16777215))
84 |
85 | self.horizontalLayout.addWidget(self.open_rtsp)
86 |
87 | self.openvideo = QPushButton(self.groupBox_2)
88 | self.openvideo.setObjectName(u"openvideo")
89 | self.openvideo.setMaximumSize(QSize(16777215, 16777215))
90 |
91 | self.horizontalLayout.addWidget(self.openvideo)
92 |
93 |
94 | self.verticalLayout_2.addLayout(self.horizontalLayout)
95 |
96 |
97 | self.gridLayout_4.addLayout(self.verticalLayout_2, 0, 0, 1, 1)
98 |
99 |
100 | self.verticalLayout.addWidget(self.groupBox_2)
101 |
102 |
103 | self.gridLayout_2.addLayout(self.verticalLayout, 0, 0, 1, 1)
104 |
105 |
106 | self.retranslateUi(video_pre_settings)
107 |
108 | QMetaObject.connectSlotsByName(video_pre_settings)
109 | # setupUi
110 |
111 | def retranslateUi(self, video_pre_settings):
112 | video_pre_settings.setWindowTitle(QCoreApplication.translate("video_pre_settings", u"Form", None))
113 | self.groupBox.setTitle(QCoreApplication.translate("video_pre_settings", u"\u89c6\u9891\u9884\u89c8", None))
114 | self.groupBox_2.setTitle("")
115 | self.label.setText(QCoreApplication.translate("video_pre_settings", u"\u89c6\u9891\u8def\u5f84", None))
116 | self.label_2.setText(QCoreApplication.translate("video_pre_settings", u"\u8df3\u8fc7\u8fc7\u5e27\u6570", None))
117 | self.open_rtsp.setText(QCoreApplication.translate("video_pre_settings", u"\u6253\u5f00\u8bbe\u5907", None))
118 | self.openvideo.setText(QCoreApplication.translate("video_pre_settings", u"\u6253\u5f00\u6587\u4ef6", None))
119 | # retranslateUi
120 |
121 |
--------------------------------------------------------------------------------
/tools/video_compare/video_pre_settings.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | video_pre_settings
4 |
5 |
6 |
7 | 0
8 | 0
9 | 310
10 | 220
11 |
12 |
13 |
14 |
15 | 16777215
16 | 16777215
17 |
18 |
19 |
20 | Form
21 |
22 |
23 | -
24 |
25 |
-
26 |
27 |
28 | 视频预览
29 |
30 |
31 |
-
32 |
33 |
34 |
35 |
36 |
37 | -
38 |
39 |
40 |
41 | 16777215
42 | 113
43 |
44 |
45 |
46 |
47 |
48 |
49 |
-
50 |
51 |
-
52 |
53 |
-
54 |
55 |
56 |
57 | 16777215
58 | 16777215
59 |
60 |
61 |
62 | 视频路径
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 | 16777215
71 | 16777215
72 |
73 |
74 |
75 |
76 | -
77 |
78 |
79 |
80 | 16777215
81 | 16777215
82 |
83 |
84 |
85 | 跳过过帧数
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 | 16777215
94 | 16777215
95 |
96 |
97 |
98 | 10000
99 |
100 |
101 | 10
102 |
103 |
104 |
105 |
106 |
107 | -
108 |
109 |
-
110 |
111 |
112 |
113 | 16777215
114 | 16777215
115 |
116 |
117 |
118 | 打开设备
119 |
120 |
121 |
122 | -
123 |
124 |
125 |
126 | 16777215
127 | 16777215
128 |
129 |
130 |
131 | 打开文件
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/tools/video_compare/videocompare.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | from PySide2.QtWidgets import QFileDialog, QMessageBox, QDialog, QWidget
3 | from PySide2.QtGui import QImage, QPixmap
4 | from PySide2.QtCore import QTimer
5 | from tools.video_compare.shake_test_window import Ui_ShakeTestWindow
6 | from tools.video_compare.rtspconfigview import Ui_RtspConfigView
7 | from components.customwidget import VideoView
8 | from components.window import SubWindow
9 | from tools.video_compare.videoview import VideoCompareView
10 | import numpy as np
11 | import math
12 | import os
13 |
14 |
15 | class VideoCompare(SubWindow):
16 | def __init__(self, name='VideoCompare', parent=None):
17 | super().__init__(name, parent, Ui_ShakeTestWindow())
18 | self.videoview = [VideoCompareView(self.ui.horizontalLayout)]
19 | self.videoview.append(VideoCompareView(self.ui.horizontalLayout))
20 | #self.videoview[0].setupCbFunc(self.open_video, self.open_rtsp, self.open_video_path)
21 | self.ui.actionstart.triggered.connect(self.start_video)
22 | self.ui.actionpause.triggered.connect(self.pause_video)
23 | self.ui.actionrestart.triggered.connect(self.restart_video)
24 | self.ui.actionadd.triggered.connect(self.add_video)
25 | self.ui.actionsubtract.triggered.connect(self.remove_video)
26 | self.ui.actionspeedup.triggered.connect(self.speed_up_video)
27 | self.ui.actionspeeddown.triggered.connect(self.speed_down_video)
28 | self.speed = 1.0
29 | self.ui.statusbar.showMessage("播放速度 {}".format(self.speed))
30 |
31 | def speed_down_video(self):
32 | if(self.speed < 3):
33 | self.speed += 0.1
34 | self.ui.statusbar.showMessage("播放速度 {:.2f}".format(1/self.speed))
35 | for video in self.videoview:
36 | video.set_speed(self.speed)
37 | else:
38 | return
39 |
40 | def speed_up_video(self):
41 | if(self.speed > 0.3):
42 | self.speed -= 0.1
43 | self.ui.statusbar.showMessage("播放速度 {:.2f}".format(1/self.speed))
44 | for video in self.videoview:
45 | video.set_speed(self.speed)
46 | else:
47 | return
48 |
49 | def add_video(self):
50 | self.videoview.append(VideoCompareView(self.ui.horizontalLayout))
51 |
52 | def remove_video(self):
53 | remove_widget = self.videoview.pop()
54 | # 必须要加这一句才能彻底移除!
55 | remove_widget.widget.setParent(None)
56 | self.ui.horizontalLayout.removeWidget(remove_widget.widget)
57 |
58 | def start_video(self):
59 | for video in self.videoview:
60 | video.start_video()
61 |
62 | def pause_video(self):
63 | for video in self.videoview:
64 | video.stop_video()
65 |
66 | def restart_video(self):
67 | for video in self.videoview:
68 | video.restart_video()
69 |
70 | def set_ui_enable(self, value):
71 | self.ui.set_roi_up.setEnabled(value)
72 | self.ui.set_roi_down.setEnabled(value)
73 | self.ui.direction_select.setEnabled(value)
74 | self.ui.skipframes.setEnabled(value)
75 | self.ui.remove_move_point_enable.setEnabled(value)
76 | self.ui.calc_inter_frams.setEnabled(value)
77 |
78 |
79 | class RtspConfigView(QDialog):
80 | def __init__(self, parent):
81 | super().__init__()
82 | self.parent = parent
83 |
84 | def closeEvent(self, event):
85 | return super().closeEvent(event)
86 |
--------------------------------------------------------------------------------
/tools/video_compare/videoview.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QDialog, QWidget, QGraphicsScene, QFileDialog, QMessageBox
2 | from PySide2.QtGui import QImage, QPixmap
3 | from PySide2.QtCore import QTimer
4 | from components.customwidget import ImageView
5 | from tools.video_compare.video_pre_settings import Ui_video_pre_settings
6 | from tools.video_compare.rtspconfigview import Ui_RtspConfigView
7 | import os
8 | import cv2
9 |
10 |
11 | class VideoCompareView(object):
12 | def __init__(self, parent=None):
13 | self.widget = QWidget()
14 | self.setting_widget = Ui_video_pre_settings()
15 | self.setting_widget.setupUi(self.widget)
16 | self.scene = QGraphicsScene()
17 | self.imageview = ImageView(self.scene, self.widget)
18 | self.setting_widget.videoview.addWidget(self.imageview)
19 | parent.addWidget(self.widget)
20 | # init params
21 | self.video_valid = False
22 | self.process_speed = 33
23 | self.skip_frames = 0
24 | self.processing_video = False
25 | self.video_timer = QTimer()
26 | self.video_timer.timeout.connect(self.open_frame)
27 | # init func
28 | self.setting_widget.openvideo.clicked.connect(self.open_video)
29 | self.setting_widget.open_rtsp.clicked.connect(self.open_rtsp)
30 | self.setting_widget.skipframe.valueChanged.connect(self.set_skip_frame)
31 | self.imageview.sigDragEvent.connect(self.open_video_path)
32 |
33 | def set_skip_frame(self, value):
34 | # self.skip_frames = self.setting_widget.skipframe.value()
35 | self.skip_frames = value
36 | self.vertify_video()
37 |
38 | def open_video(self):
39 | videopath = QFileDialog.getOpenFileName(
40 | None, '打开文件', './', 'video files(*.mp4)')
41 | if(videopath[0] != ''):
42 | self.open_video_path(videopath[0])
43 |
44 | def open_video_path(self, string):
45 | self.setting_widget.path.setText(string)
46 | self.vertify_video()
47 | # self.set_ui_enable(True)
48 |
49 | def open_rtsp(self):
50 | self.rtsp_config_window = QDialog()
51 | self.rtsp_config_ui = Ui_RtspConfigView()
52 | self.rtsp_config_ui.setupUi(self.rtsp_config_window)
53 | self.rtsp_config_window.show()
54 | self.rtsp_config_ui.buttonBox.clicked.connect(self.rtsp_config)
55 |
56 | def rtsp_config(self):
57 | username = self.rtsp_config_ui.username.text()
58 | password = self.rtsp_config_ui.password.text()
59 | ip = self.rtsp_config_ui.ip.text()
60 | port = self.rtsp_config_ui.port.text()
61 | # 移动设备需要通过adb映射端口
62 | if(self.rtsp_config_ui.isphoto.isChecked() == True):
63 | command = "forward tcp:" + port + ' ' + "tcp:" + port
64 | os.system("adb " + command)
65 | os.system("kdb " + command)
66 | rtsp_path = "rtsp://"+username+":"+password+"@"+ip+":"+port
67 | self.open_video_path(rtsp_path)
68 |
69 | def vertify_video(self):
70 | # 输出参数初始化
71 | # self.frame_count = 0
72 | self.vidcap = cv2.VideoCapture(self.setting_widget.path.text())
73 | # 片头调过多少帧
74 | self.vidcap.set(cv2.CAP_PROP_POS_FRAMES, self.skip_frames)
75 | success, frame = self.vidcap.read()
76 | if success:
77 | self.display(frame)
78 | self.video_valid = True
79 | else:
80 | self.critical_window_show('视频打不开')
81 | self.video_valid = False
82 | return
83 |
84 | def display(self, img):
85 | self.scene.clear()
86 | self.scene.addPixmap(
87 | QPixmap(QImage(img, img.shape[1], img.shape[0], QImage.Format_BGR888)))
88 |
89 | def open_frame(self):
90 | success, frame = self.vidcap.read()
91 | if success:
92 | # self.frame_count += 1
93 | self.display(frame)
94 | else:
95 | self.video_timer.stop()
96 | self.processing_video = False
97 | self.video_valid = False
98 |
99 | def set_speed(self, value):
100 | speed = value * 33
101 | if (self.processing_video == True and int(speed) != self.process_speed):
102 | self.process_speed = int(speed)
103 | self.stop_video()
104 | self.start_video()
105 |
106 | def start_video(self):
107 | if (self.video_valid == True):
108 | self.processing_video = True
109 | # 增加定时器,每100ms进行一帧的处理
110 | self.video_timer.start(self.process_speed)
111 |
112 | def stop_video(self):
113 | self.processing_video = False
114 | self.video_timer.stop()
115 |
116 | def restart_video(self):
117 | self.stop_video()
118 | self.vertify_video()
119 | self.start_video()
120 |
121 | def critical_window_show(self, str):
122 | reply = QMessageBox.critical(
123 | self.widget, '警告', str,
124 | QMessageBox.Yes, QMessageBox.Yes)
125 | return
126 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/Readme.md:
--------------------------------------------------------------------------------
1 | # YUV 图片查看工具
2 |
3 | 这是一款能够对 YUV 图像进行分析以及简单的处理工具
4 |
5 | ## 背景
6 |
7 | 1. 经常需要抓取 yuv,又没有比较好的工具,能够打开比较多的格式,并且保存成 jpg
8 | 2. 多张 yuv 之间的对比也比较麻烦
9 |
10 | ## 将要实现的功能点
11 |
12 | [ ] 能够支持常见的 yuv 格式
13 | [ ] 打开或者拖动的时候,会提示选取 yuv 格式,并保存最近一次格式
14 | [ ] 能够保存成 jpg 格式
15 | [ ] 能够通过鼠标对图片放大和缩小
16 | [ ] 能够通过鼠标对像素点的信息进行显示
17 | [ ] 能够通过直方图等统计分析对选中区域进行分析
18 | [ ] 能够支持多张图片对比,按+号,可以添加图片窗口
19 | [ ] 多张图片拖动与放大应该保持一致
20 | [ ] 能够支持批量 yuv 图片转换成 jpg 的功能
21 | [ ] 能够按上下键去查看上下的 yuv
22 | [ ] 能够支持多帧 yuv 的解析,通过数字或者滑动条去选择相应帧的图片显示
23 | [ ] 支持多张图片的偏移调整
24 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoqinxing/ImageTools/7ddf141c10fb55c0be4babb1004776e112a9f330/tools/yuv_viewer/__init__.py
--------------------------------------------------------------------------------
/tools/yuv_viewer/ui/yuvconfig.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'yuvconfig.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import *
12 | from PySide2.QtGui import *
13 | from PySide2.QtWidgets import *
14 |
15 |
16 | class Ui_YUVConfig(object):
17 | def setupUi(self, YUVConfig):
18 | if not YUVConfig.objectName():
19 | YUVConfig.setObjectName(u"YUVConfig")
20 | YUVConfig.resize(212, 175)
21 | self.verticalLayout = QVBoxLayout(YUVConfig)
22 | self.verticalLayout.setObjectName(u"verticalLayout")
23 | self.groupBox = QGroupBox(YUVConfig)
24 | self.groupBox.setObjectName(u"groupBox")
25 | self.verticalLayout_2 = QVBoxLayout(self.groupBox)
26 | self.verticalLayout_2.setObjectName(u"verticalLayout_2")
27 | self.formLayout = QFormLayout()
28 | self.formLayout.setObjectName(u"formLayout")
29 | self.label_2 = QLabel(self.groupBox)
30 | self.label_2.setObjectName(u"label_2")
31 |
32 | self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_2)
33 |
34 | self.label_3 = QLabel(self.groupBox)
35 | self.label_3.setObjectName(u"label_3")
36 |
37 | self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_3)
38 |
39 | self.yuvformat = QComboBox(self.groupBox)
40 | self.yuvformat.addItem("")
41 | self.yuvformat.addItem("")
42 | self.yuvformat.addItem("")
43 | self.yuvformat.addItem("")
44 | self.yuvformat.addItem("")
45 | self.yuvformat.addItem("")
46 | self.yuvformat.addItem("")
47 | self.yuvformat.addItem("")
48 | self.yuvformat.setObjectName(u"yuvformat")
49 |
50 | self.formLayout.setWidget(2, QFormLayout.FieldRole, self.yuvformat)
51 |
52 | self.width = QSpinBox(self.groupBox)
53 | self.width.setObjectName(u"width")
54 | self.width.setMaximum(8192)
55 | self.width.setSingleStep(10)
56 | self.width.setValue(3840)
57 |
58 | self.formLayout.setWidget(0, QFormLayout.FieldRole, self.width)
59 |
60 | self.label = QLabel(self.groupBox)
61 | self.label.setObjectName(u"label")
62 |
63 | self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label)
64 |
65 | self.height = QSpinBox(self.groupBox)
66 | self.height.setObjectName(u"height")
67 | self.height.setMaximum(8192)
68 | self.height.setSingleStep(10)
69 | self.height.setValue(2160)
70 |
71 | self.formLayout.setWidget(1, QFormLayout.FieldRole, self.height)
72 |
73 |
74 | self.verticalLayout_2.addLayout(self.formLayout)
75 |
76 |
77 | self.verticalLayout.addWidget(self.groupBox)
78 |
79 | self.saveimg_in_rotate = QCheckBox(YUVConfig)
80 | self.saveimg_in_rotate.setObjectName(u"saveimg_in_rotate")
81 | self.saveimg_in_rotate.setChecked(True)
82 |
83 | self.verticalLayout.addWidget(self.saveimg_in_rotate)
84 |
85 | self.buttonBox = QDialogButtonBox(YUVConfig)
86 | self.buttonBox.setObjectName(u"buttonBox")
87 | self.buttonBox.setOrientation(Qt.Horizontal)
88 | self.buttonBox.setStandardButtons(QDialogButtonBox.Ok)
89 |
90 | self.verticalLayout.addWidget(self.buttonBox)
91 |
92 |
93 | self.retranslateUi(YUVConfig)
94 | self.buttonBox.accepted.connect(YUVConfig.accept)
95 | self.buttonBox.rejected.connect(YUVConfig.reject)
96 |
97 | QMetaObject.connectSlotsByName(YUVConfig)
98 | # setupUi
99 |
100 | def retranslateUi(self, YUVConfig):
101 | YUVConfig.setWindowTitle(QCoreApplication.translate("YUVConfig", u"\u56fe\u7247\u67e5\u770b\u5de5\u5177\u914d\u7f6e", None))
102 | self.groupBox.setTitle(QCoreApplication.translate("YUVConfig", u"YUV\u914d\u7f6e", None))
103 | self.label_2.setText(QCoreApplication.translate("YUVConfig", u"\u5bbd", None))
104 | self.label_3.setText(QCoreApplication.translate("YUVConfig", u"\u683c\u5f0f", None))
105 | self.yuvformat.setItemText(0, QCoreApplication.translate("YUVConfig", u"NV21", None))
106 | self.yuvformat.setItemText(1, QCoreApplication.translate("YUVConfig", u"NV12", None))
107 | self.yuvformat.setItemText(2, QCoreApplication.translate("YUVConfig", u"YCrCb", None))
108 | self.yuvformat.setItemText(3, QCoreApplication.translate("YUVConfig", u"YUV420", None))
109 | self.yuvformat.setItemText(4, QCoreApplication.translate("YUVConfig", u"YUV422", None))
110 | self.yuvformat.setItemText(5, QCoreApplication.translate("YUVConfig", u"UYVY", None))
111 | self.yuvformat.setItemText(6, QCoreApplication.translate("YUVConfig", u"YUYV", None))
112 | self.yuvformat.setItemText(7, QCoreApplication.translate("YUVConfig", u"YVYU", None))
113 |
114 | self.label.setText(QCoreApplication.translate("YUVConfig", u"\u9ad8", None))
115 | self.saveimg_in_rotate.setText(QCoreApplication.translate("YUVConfig", u"\u65cb\u8f6c\u65f6\u4fdd\u5b58\u56fe\u7247", None))
116 | # retranslateUi
117 |
118 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/ui/yuvconfig.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | YUVConfig
4 |
5 |
6 |
7 | 0
8 | 0
9 | 212
10 | 175
11 |
12 |
13 |
14 | 图片查看工具配置
15 |
16 |
17 | -
18 |
19 |
20 | YUV配置
21 |
22 |
23 |
-
24 |
25 |
-
26 |
27 |
28 | 宽
29 |
30 |
31 |
32 | -
33 |
34 |
35 | 格式
36 |
37 |
38 |
39 | -
40 |
41 |
-
42 |
43 | NV21
44 |
45 |
46 | -
47 |
48 | NV12
49 |
50 |
51 | -
52 |
53 | YCrCb
54 |
55 |
56 | -
57 |
58 | YUV420
59 |
60 |
61 | -
62 |
63 | YUV422
64 |
65 |
66 | -
67 |
68 | UYVY
69 |
70 |
71 | -
72 |
73 | YUYV
74 |
75 |
76 | -
77 |
78 | YVYU
79 |
80 |
81 |
82 |
83 | -
84 |
85 |
86 | 8192
87 |
88 |
89 | 10
90 |
91 |
92 | 3840
93 |
94 |
95 |
96 | -
97 |
98 |
99 | 高
100 |
101 |
102 |
103 | -
104 |
105 |
106 | 8192
107 |
108 |
109 | 10
110 |
111 |
112 | 2160
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | -
122 |
123 |
124 | 旋转时保存图片
125 |
126 |
127 | true
128 |
129 |
130 |
131 | -
132 |
133 |
134 | Qt::Horizontal
135 |
136 |
137 | QDialogButtonBox::Ok
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | buttonBox
147 | accepted()
148 | YUVConfig
149 | accept()
150 |
151 |
152 | 248
153 | 254
154 |
155 |
156 | 157
157 | 274
158 |
159 |
160 |
161 |
162 | buttonBox
163 | rejected()
164 | YUVConfig
165 | reject()
166 |
167 |
168 | 316
169 | 260
170 |
171 |
172 | 286
173 | 274
174 |
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/ui/yuvviewer_window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'yuvviewer_window.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 5.15.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide2.QtCore import *
12 | from PySide2.QtGui import *
13 | from PySide2.QtWidgets import *
14 |
15 | import resource_rc
16 |
17 | class Ui_YUVEditor(object):
18 | def setupUi(self, YUVEditor):
19 | if not YUVEditor.objectName():
20 | YUVEditor.setObjectName(u"YUVEditor")
21 | YUVEditor.resize(800, 600)
22 | icon = QIcon()
23 | icon.addFile(u":/tool_icon/resource/main.png", QSize(), QIcon.Normal, QIcon.Off)
24 | YUVEditor.setWindowIcon(icon)
25 | YUVEditor.setToolTipDuration(-1)
26 | YUVEditor.setAnimated(True)
27 | YUVEditor.setDockOptions(QMainWindow.AllowTabbedDocks|QMainWindow.AnimatedDocks|QMainWindow.GroupedDragging|QMainWindow.VerticalTabs)
28 | self.saveimage = QAction(YUVEditor)
29 | self.saveimage.setObjectName(u"saveimage")
30 | icon1 = QIcon()
31 | icon1.addFile(u":/tool_icon/resource/save_icon.png", QSize(), QIcon.Normal, QIcon.Off)
32 | self.saveimage.setIcon(icon1)
33 | self.openimage = QAction(YUVEditor)
34 | self.openimage.setObjectName(u"openimage")
35 | icon2 = QIcon()
36 | icon2.addFile(u":/tool_icon/resource/open.png", QSize(), QIcon.Normal, QIcon.Off)
37 | self.openimage.setIcon(icon2)
38 | self.actionstats = QAction(YUVEditor)
39 | self.actionstats.setObjectName(u"actionstats")
40 | icon3 = QIcon()
41 | icon3.addFile(u":/tool_icon/resource/stats.png", QSize(), QIcon.Normal, QIcon.Off)
42 | self.actionstats.setIcon(icon3)
43 | self.nextphoto = QAction(YUVEditor)
44 | self.nextphoto.setObjectName(u"nextphoto")
45 | icon4 = QIcon()
46 | icon4.addFile(u":/tool_icon/resource/down.png", QSize(), QIcon.Normal, QIcon.Off)
47 | self.nextphoto.setIcon(icon4)
48 | self.prephoto = QAction(YUVEditor)
49 | self.prephoto.setObjectName(u"prephoto")
50 | icon5 = QIcon()
51 | icon5.addFile(u":/tool_icon/resource/up.png", QSize(), QIcon.Normal, QIcon.Off)
52 | self.prephoto.setIcon(icon5)
53 | self.deletephoto = QAction(YUVEditor)
54 | self.deletephoto.setObjectName(u"deletephoto")
55 | icon6 = QIcon()
56 | icon6.addFile(u":/tool_icon/resource/delete.png", QSize(), QIcon.Normal, QIcon.Off)
57 | self.deletephoto.setIcon(icon6)
58 | self.add_compare = QAction(YUVEditor)
59 | self.add_compare.setObjectName(u"add_compare")
60 | icon7 = QIcon()
61 | icon7.addFile(u":/tool_icon/resource/add.png", QSize(), QIcon.Normal, QIcon.Off)
62 | self.add_compare.setIcon(icon7)
63 | self.rotateright = QAction(YUVEditor)
64 | self.rotateright.setObjectName(u"rotateright")
65 | icon8 = QIcon()
66 | icon8.addFile(u":/tool_icon/resource/right.png", QSize(), QIcon.Normal, QIcon.Off)
67 | self.rotateright.setIcon(icon8)
68 | self.yuvconfig = QAction(YUVEditor)
69 | self.yuvconfig.setObjectName(u"yuvconfig")
70 | icon9 = QIcon()
71 | icon9.addFile(u":/tool_icon/resource/config.png", QSize(), QIcon.Normal, QIcon.Off)
72 | self.yuvconfig.setIcon(icon9)
73 | self.centralwidget = QWidget(YUVEditor)
74 | self.centralwidget.setObjectName(u"centralwidget")
75 | self.gridLayout_2 = QGridLayout(self.centralwidget)
76 | self.gridLayout_2.setObjectName(u"gridLayout_2")
77 | self.photo_title = QGroupBox(self.centralwidget)
78 | self.photo_title.setObjectName(u"photo_title")
79 | self.gridLayout_3 = QGridLayout(self.photo_title)
80 | self.gridLayout_3.setObjectName(u"gridLayout_3")
81 | self.gridLayout = QGridLayout()
82 | self.gridLayout.setObjectName(u"gridLayout")
83 |
84 | self.gridLayout_3.addLayout(self.gridLayout, 0, 0, 1, 1)
85 |
86 |
87 | self.gridLayout_2.addWidget(self.photo_title, 0, 0, 1, 1)
88 |
89 | YUVEditor.setCentralWidget(self.centralwidget)
90 | self.statusBar = QStatusBar(YUVEditor)
91 | self.statusBar.setObjectName(u"statusBar")
92 | self.statusBar.setMouseTracking(True)
93 | YUVEditor.setStatusBar(self.statusBar)
94 | self.toolBar = QToolBar(YUVEditor)
95 | self.toolBar.setObjectName(u"toolBar")
96 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
97 | sizePolicy.setHorizontalStretch(0)
98 | sizePolicy.setVerticalStretch(0)
99 | sizePolicy.setHeightForWidth(self.toolBar.sizePolicy().hasHeightForWidth())
100 | self.toolBar.setSizePolicy(sizePolicy)
101 | self.toolBar.setAllowedAreas(Qt.AllToolBarAreas)
102 | self.toolBar.setOrientation(Qt.Vertical)
103 | YUVEditor.addToolBar(Qt.RightToolBarArea, self.toolBar)
104 |
105 | self.toolBar.addAction(self.openimage)
106 | self.toolBar.addAction(self.saveimage)
107 | self.toolBar.addAction(self.yuvconfig)
108 | self.toolBar.addAction(self.deletephoto)
109 | self.toolBar.addAction(self.actionstats)
110 | self.toolBar.addAction(self.prephoto)
111 | self.toolBar.addAction(self.nextphoto)
112 | self.toolBar.addAction(self.rotateright)
113 | self.toolBar.addAction(self.add_compare)
114 |
115 | self.retranslateUi(YUVEditor)
116 |
117 | QMetaObject.connectSlotsByName(YUVEditor)
118 | # setupUi
119 |
120 | def retranslateUi(self, YUVEditor):
121 | YUVEditor.setWindowTitle(QCoreApplication.translate("YUVEditor", u"YUV\u56fe\u7247\u67e5\u770b\u5de5\u5177", None))
122 | #if QT_CONFIG(tooltip)
123 | YUVEditor.setToolTip("")
124 | #endif // QT_CONFIG(tooltip)
125 | self.saveimage.setText(QCoreApplication.translate("YUVEditor", u"save", None))
126 | #if QT_CONFIG(shortcut)
127 | self.saveimage.setShortcut(QCoreApplication.translate("YUVEditor", u"Ctrl+S", None))
128 | #endif // QT_CONFIG(shortcut)
129 | self.openimage.setText(QCoreApplication.translate("YUVEditor", u"open", None))
130 | #if QT_CONFIG(tooltip)
131 | self.openimage.setToolTip(QCoreApplication.translate("YUVEditor", u"open", None))
132 | #endif // QT_CONFIG(tooltip)
133 | self.actionstats.setText(QCoreApplication.translate("YUVEditor", u"\u7edf\u8ba1\u4fe1\u606f", None))
134 | #if QT_CONFIG(tooltip)
135 | self.actionstats.setToolTip(QCoreApplication.translate("YUVEditor", u"\u7edf\u8ba1\u4fe1\u606f", None))
136 | #endif // QT_CONFIG(tooltip)
137 | self.nextphoto.setText(QCoreApplication.translate("YUVEditor", u"\u4e0b\u4e00\u4e2a\u56fe\u7247", None))
138 | #if QT_CONFIG(shortcut)
139 | self.nextphoto.setShortcut(QCoreApplication.translate("YUVEditor", u"Down", None))
140 | #endif // QT_CONFIG(shortcut)
141 | self.prephoto.setText(QCoreApplication.translate("YUVEditor", u"\u4e0a\u4e00\u5f20\u56fe\u7247", None))
142 | #if QT_CONFIG(shortcut)
143 | self.prephoto.setShortcut(QCoreApplication.translate("YUVEditor", u"Up", None))
144 | #endif // QT_CONFIG(shortcut)
145 | self.deletephoto.setText(QCoreApplication.translate("YUVEditor", u"\u5220\u9664", None))
146 | #if QT_CONFIG(tooltip)
147 | self.deletephoto.setToolTip(QCoreApplication.translate("YUVEditor", u"\u5220\u9664", None))
148 | #endif // QT_CONFIG(tooltip)
149 | #if QT_CONFIG(shortcut)
150 | self.deletephoto.setShortcut(QCoreApplication.translate("YUVEditor", u"Del", None))
151 | #endif // QT_CONFIG(shortcut)
152 | self.add_compare.setText(QCoreApplication.translate("YUVEditor", u"\u6dfb\u52a0\u5bf9\u6bd4\u56fe\u7247", None))
153 | self.rotateright.setText(QCoreApplication.translate("YUVEditor", u"\u987a\u65f6\u9488\u65cb\u8f6c", None))
154 | self.yuvconfig.setText(QCoreApplication.translate("YUVEditor", u"YUV\u914d\u7f6e", None))
155 | self.photo_title.setTitle("")
156 | self.toolBar.setWindowTitle(QCoreApplication.translate("YUVEditor", u"toolBar_2", None))
157 | # retranslateUi
158 |
159 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/ui/yuvviewer_window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | YUVEditor
4 |
5 |
6 |
7 | 0
8 | 0
9 | 800
10 | 600
11 |
12 |
13 |
14 | YUV图片查看工具
15 |
16 |
17 |
18 | :/tool_icon/resource/main.png:/tool_icon/resource/main.png
19 |
20 |
21 |
22 |
23 |
24 | -1
25 |
26 |
27 | true
28 |
29 |
30 | QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks|QMainWindow::GroupedDragging|QMainWindow::VerticalTabs
31 |
32 |
33 |
34 | -
35 |
36 |
37 |
38 |
39 |
40 |
-
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | true
51 |
52 |
53 |
54 |
55 |
56 | 0
57 | 0
58 |
59 |
60 |
61 | toolBar_2
62 |
63 |
64 | Qt::AllToolBarAreas
65 |
66 |
67 | Qt::Vertical
68 |
69 |
70 | RightToolBarArea
71 |
72 |
73 | false
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | :/tool_icon/resource/save_icon.png:/tool_icon/resource/save_icon.png
89 |
90 |
91 | save
92 |
93 |
94 | Ctrl+S
95 |
96 |
97 |
98 |
99 |
100 | :/tool_icon/resource/open.png:/tool_icon/resource/open.png
101 |
102 |
103 | open
104 |
105 |
106 | open
107 |
108 |
109 |
110 |
111 |
112 | :/tool_icon/resource/stats.png:/tool_icon/resource/stats.png
113 |
114 |
115 | 统计信息
116 |
117 |
118 | 统计信息
119 |
120 |
121 |
122 |
123 |
124 | :/tool_icon/resource/down.png:/tool_icon/resource/down.png
125 |
126 |
127 | 下一个图片
128 |
129 |
130 | Down
131 |
132 |
133 |
134 |
135 |
136 | :/tool_icon/resource/up.png:/tool_icon/resource/up.png
137 |
138 |
139 | 上一张图片
140 |
141 |
142 | Up
143 |
144 |
145 |
146 |
147 |
148 | :/tool_icon/resource/delete.png:/tool_icon/resource/delete.png
149 |
150 |
151 | 删除
152 |
153 |
154 | 删除
155 |
156 |
157 | Del
158 |
159 |
160 |
161 |
162 |
163 | :/tool_icon/resource/add.png:/tool_icon/resource/add.png
164 |
165 |
166 | 添加对比图片
167 |
168 |
169 |
170 |
171 |
172 | :/tool_icon/resource/right.png:/tool_icon/resource/right.png
173 |
174 |
175 | 顺时针旋转
176 |
177 |
178 |
179 |
180 |
181 | :/tool_icon/resource/config.png:/tool_icon/resource/config.png
182 |
183 |
184 | YUV配置
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/yuv_config.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QDialog
2 | from PySide2.QtGui import Qt
3 | from PySide2.QtCore import Signal
4 | from .ui.yuvconfig import Ui_YUVConfig
5 |
6 |
7 | class YUVConfig(QDialog):
8 | height = 2160
9 | width = 3840
10 | yuv_format = 'NV21'
11 | need_saveimg_in_rotate = True
12 | configUpdateEvent = Signal()
13 |
14 | def __init__(self, parent=None) -> None:
15 | super().__init__(parent)
16 | self.ui = Ui_YUVConfig()
17 | self.ui.setupUi(self)
18 | self.ui.buttonBox.clicked.connect(self.get)
19 |
20 | def set(self):
21 | self.ui.width.setValue(self.width)
22 | self.ui.height.setValue(self.height)
23 | index = self.ui.yuvformat.findText(self.yuv_format)
24 | self.ui.yuvformat.setCurrentIndex(index)
25 | self.ui.saveimg_in_rotate.setCheckState(
26 | Qt.Checked if self.need_saveimg_in_rotate else Qt.Unchecked)
27 |
28 | def get(self):
29 | self.height = self.ui.height.value()
30 | self.width = self.ui.width.value()
31 | self.yuv_format = self.ui.yuvformat.currentText()
32 | self.need_saveimg_in_rotate = (
33 | self.ui.saveimg_in_rotate.checkState() == Qt.Checked)
34 | self.configUpdateEvent.emit()
35 |
--------------------------------------------------------------------------------
/tools/yuv_viewer/yuv_viewer.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QGraphicsScene, QFileDialog
2 | from components.customwidget import ImageView
3 | from components.status_code_enum import ImageToolError
4 | from components.window import SubWindow
5 | from components.histview import HistView
6 | from .ui.yuvviewer_window import Ui_YUVEditor
7 | from components.BasicImage import ImageBasic
8 | from .yuv_config import YUVConfig
9 |
10 |
11 | class YUVViewer(SubWindow):
12 | def __init__(self, name='YUVViewer', parent=None):
13 | super().__init__(name, parent, Ui_YUVEditor())
14 | self.x = 0
15 | self.y = 0
16 | self.img = ImageBasic()
17 | self.hist_window = None
18 | self.img_index_str = ''
19 | self.config = YUVConfig()
20 | self.config.configUpdateEvent.connect(
21 | lambda: self.__init_img(self.img.imgpath)) # 配置界面参数更新
22 |
23 | self.scene = QGraphicsScene()
24 | self.imageview = ImageView(self.scene, parent)
25 | # 由于graphicsView被自定义了,需要重新定义一下UI,gridlayout还需要重新加一下widget
26 | self.ui.gridLayout.addWidget(self.imageview, 0, 1, 3, 1)
27 | self.imageview.sigDragEvent.connect(self.__init_img)
28 | self.imageview.sigMouseMovePoint.connect(self.show_point_rgb)
29 | self.imageview.sigWheelEvent.connect(self.__show_point_stats)
30 | self.ui.openimage.triggered.connect(self.on_open_img)
31 | self.ui.saveimage.triggered.connect(self.save_now_image)
32 | self.ui.actionstats.triggered.connect(self.on_calc_stats)
33 | self.ui.nextphoto.triggered.connect(self.switch_next_photo)
34 | self.ui.prephoto.triggered.connect(self.switch_pre_photo)
35 | self.imageview.rubberBandChanged.connect(self.update_stats_range)
36 | self.ui.deletephoto.triggered.connect(self.delete_photo)
37 | self.ui.rotateright.triggered.connect(self.rotate_photo)
38 | self.ui.yuvconfig.triggered.connect(self.config.show) # 配置UI显示
39 |
40 | def delete_photo(self):
41 | self.img.remove_image()
42 | next_photo, index, files_nums = self.img.find_next_time_photo(1)
43 | self.img_index_str = "({}/{})".format(index, files_nums - 1)
44 | self.__init_img(next_photo, self.img_index_str)
45 |
46 | def switch_next_photo(self):
47 | next_photo, index, files_nums = self.img.find_next_time_photo(1)
48 | self.img_index_str = "({}/{})".format(index + 1, files_nums)
49 | self.__init_img(next_photo, self.img_index_str)
50 |
51 | def switch_pre_photo(self):
52 | pre_photo, index, files_nums = self.img.find_next_time_photo(-1)
53 | self.img_index_str = "({}/{})".format(index + 1, files_nums)
54 | self.__init_img(pre_photo, self.img_index_str)
55 |
56 | def rotate_photo(self):
57 | try:
58 | self.img.rotate90()
59 | self.__display_img(self.img_index_str)
60 | if self.config.need_saveimg_in_rotate is True:
61 | self.img.save_image(self.img.imgpath)
62 | except ImageToolError as e:
63 | e.show()
64 |
65 | def on_open_img(self):
66 | imagepath = QFileDialog.getOpenFileName(
67 | None, '打开图片', self.img.get_dir(), "Images (*.jpg *.png *.bmp *.yuv)")
68 | if imagepath[0] != '':
69 | self.__init_img(imagepath[0])
70 |
71 | def save_now_image(self):
72 | try:
73 | imagepath = QFileDialog.getSaveFileName(
74 | None, '保存图片', self.img.get_dir(), "Images (*.jpg)")
75 | if imagepath[0] != '':
76 | self.img.save_image(imagepath[0])
77 | except ImageToolError as e:
78 | e.show()
79 |
80 | def __init_img(self, filename, indexstr=''):
81 | try:
82 | self.img.load_yuv_config(
83 | self.config.width, self.config.height, self.config.yuv_format)
84 | self.img.load_file(filename)
85 | self.__display_img(indexstr)
86 | except ImageToolError as e:
87 | e.show()
88 |
89 | def __display_img(self, indexstr=''):
90 | try:
91 | self.img.display_in_scene(self.scene)
92 | self.ui.photo_title.setTitle(indexstr + self.img.imgpath)
93 | if self.hist_window is not None and self.hist_window.enable is True:
94 | self.hist_window.update_rect_data(self.img.img, self.rect)
95 | except ImageToolError as e:
96 | e.show()
97 |
98 | def __show_point_stats(self):
99 | if self.img.img is not None:
100 | rgb = self.img.get_img_point(self.x, self.y)
101 | if (rgb is not None):
102 | scale_ratio = int(self.imageview.scale_ratio * 100)
103 | self.ui.statusBar.showMessage(
104 | "x:{},y:{} : R:{} G:{} B:{} 缩放比例:{}%".format(self.x, self.y, rgb[2], rgb[1], rgb[0], scale_ratio))
105 |
106 | def update_stats_range(self, _, fromScenePoint, toScenePoint):
107 | if(toScenePoint.x() == 0 and toScenePoint.y() == 0
108 | and self.rect[2] > self.rect[0] and self.rect[3] > self.rect[1]):
109 | if self.hist_window is not None:
110 | self.hist_window.update_rect_data(self.img.img, self.rect)
111 | else:
112 | self.rect = [int(fromScenePoint.x()), int(fromScenePoint.y()), int(
113 | toScenePoint.x()), int(toScenePoint.y())]
114 | return
115 |
116 | def show_point_rgb(self, point):
117 | """func: 鼠标移动的回调"""
118 | self.x = int(point.x())
119 | self.y = int(point.y())
120 | self.__show_point_stats()
121 |
122 | def on_calc_stats(self):
123 | """打开统计信息的窗口"""
124 | if self.img.img is not None:
125 | self.hist_window = HistView(self.imageview)
126 | self.rect = [0, 0, self.img.img.shape[1], self.img.img.shape[0]]
127 | self.hist_window.update_rect_data(self.img.img, self.rect)
128 | self.hist_window.show()
129 |
--------------------------------------------------------------------------------
/version.md:
--------------------------------------------------------------------------------
1 | **1.5.1**
2 |
3 | - 修复部分缺陷
4 |
5 | **1.5.0**
6 |
7 | - 增加在线升级的功能
8 | - 优化 RAW 图处理工具,增加 ISP 算法,优化 ISP pipeline,增强统计信息显示模块,优化多线程处理,优化算法运行报错提醒
9 | - 优化图片查看工具,增加切换图片,显示图片序号等功能
10 |
11 | **1.4.0**
12 |
13 | - 增加 RAW 图处理工具
14 |
15 | 可以查看和处理 RAW 图,已经实现了黑电平,坏点矫正,暗影矫正,去马赛克,白平衡,色彩校正,gamma,局部对比度增强,色度空间转换,对比度亮度调整,小波降噪 WNR,锐化等 ISP 算法,可以方便的进行后期算法的验证(热更新、直观的图像显示和分析)
16 |
17 | **1.3.1**
18 |
19 | - 解决不能创建备份文件的权限问题
20 | - 将比较成熟且常用的一些小工具单独发布
21 |
22 | - PQtools 转代码
23 | - 防抖测试工具
24 | - 视频对比工具
25 | - 景深计算工具
26 |
27 | 其中 Imagetools 包含了所有的工具以及说明文档,体积会大一点
28 |
29 | **1.3.0**
30 |
31 | - 优化了软件的使用细节
32 | - 增加了 pqtools 转头文件的工具
33 |
34 | **1.2.0**
35 |
36 | - 初始版本
37 |
--------------------------------------------------------------------------------