├── .gitignore ├── License ├── README.md ├── README_EN.md ├── docs ├── Download_and_usage.md ├── Download_and_usage_en.md ├── Keyboard_shortcuts.md ├── Keyboard_shortcuts_en.md ├── Usage_tutorial.md └── Usage_tutorial_en.md └── labcd ├── .editorconfig ├── CMakeLists.txt ├── LabCD.pri ├── LabCD.pro ├── LabCD.sln ├── LabCD.vcxproj ├── LabCD.vcxproj.filters ├── configs └── colormap.txt ├── i18n ├── English.qm └── English.ts ├── labcd.cpp ├── labcd.h ├── labcd.qrc ├── main.cpp ├── resources ├── AddLabel.png ├── CantDelete.png ├── Chinese.png ├── ClearMask.png ├── Color.png ├── Convert.png ├── Delete.png ├── DeleteAllPolygons.png ├── DeletePolygon.png ├── English.png ├── Enlarge.png ├── Folder.png ├── Full.png ├── Github.png ├── Help.png ├── Icon.png ├── Last.png ├── Merge.png ├── Narrow.png ├── Next.png ├── Reference.png ├── Save.png └── Split.png ├── utils ├── colormap.cpp ├── colormap.h ├── fileworker.cpp ├── fileworker.h ├── imgpress.cpp ├── imgpress.h ├── label.cpp └── label.h └── widgets ├── annotationscence.cpp ├── annotationscence.h ├── annotationview.cpp ├── annotationview.h ├── canvas.cpp ├── canvas.h ├── filelist.cpp ├── filelist.h ├── labeltable.cpp ├── labeltable.h ├── labgrid.cpp ├── labgrid.h ├── labline.cpp ├── labline.h ├── labpolygon.cpp ├── labpolygon.h ├── multcanvas.cpp ├── multcanvas.h └── opttypes.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | # Test 366 | testimgs/ 367 | 368 | # Qt build 369 | build-LabCD-*-Debug/ 370 | build-LabCD-*-Release/ 371 | 372 | # Setup 373 | setup/ 374 | 375 | # Depends 376 | labcd/depends/ 377 | 378 | # Config 379 | labcd/configs/setting.ini 380 | 381 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LabCD 2 | 3 | 简体中文 | [English](./README_EN.md) 4 | 5 | [![version](https://img.shields.io/github/release/geoyee/LabCD.svg)](https://github.com/geoyee/LabCD/releases) [![license](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE) ![Language](https://img.shields.io/badge/language-C++%2017-blue.svg) 6 | 7 | https://user-images.githubusercontent.com/71769312/216638896-bd1d5e40-6512-478d-b7b0-927c142aece6.mp4 8 | 9 | 遥感变化检测标注工具,设计参考[EISeg](https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.6/EISeg),可以分别在左右两张画布中标注两个时段影像的变化信息,将自动同步在另一画布进行显示。 10 | 11 | ## 更新 12 | 13 | 1. 增加第一次加载多光谱数据计算最佳波段组合,后续加载计算所得的波段 14 | 2. 增加切分图像块可以设置大小,并不会舍去不满一个块的部分 15 | 4. 增加合并标签图像块 16 | 6. 增加过滤空标签和图像和加载推理好的掩膜 17 | 18 | ## 依赖 19 | 20 | - Qt 6.4.0 21 | 22 | - OpenCV 4.5.5 23 | 24 | - JsonCpp 1.9.5 25 | 26 | - GDAL 3.5.3 27 | 28 | - Eigen 3.4.0 29 | 30 | ## 教程与文档 31 | 32 | - [下载与使用](./docs/Download_and_usage.md) 33 | - [快捷键](./docs/Keyboard_shortcuts.md) 34 | - [使用教程](./docs/Usage_tutorial.md) 35 | 36 | ## 资源 37 | 38 | - 部分图标:[SuperMap GIS产品彩色系功能图标库](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=32519) 39 | - 标签预定义颜色:[色板 | AntV](https://antv.vision/zh/docs/specification/language/palette) 40 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # LabCD 2 | 3 | Englis | [简体中文](./README.md) 4 | 5 | [![version](https://img.shields.io/github/release/geoyee/LabCD.svg)](https://github.com/geoyee/LabCD/releases) [![license](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE) ![Language](https://img.shields.io/badge/language-C++%2017-blue.svg) 6 | 7 | https://user-images.githubusercontent.com/71769312/216638896-bd1d5e40-6512-478d-b7b0-927c142aece6.mp4 8 | 9 | Remote sensing change detection annotation tool, it was designed with reference to [EISeg](https://github.com/PaddlePaddle/PaddleSeg/tree/release/2.6/EISeg). It can mark the change information of images in two periods in the left and right canvases respectively, and automatically display it in the other canvas synchronously. 10 | 11 | ## News 12 | 13 | 1. Add calculation of OIF for the first time loading multi-spectral data, and subsequent loading of calculated bands. 14 | 2. Add the ability to set the size of the split image blocks, without discarding any portions that do not fill a complete block. 15 | 3. Add merging of labeled image blocks. 16 | 4. Add filtering of empty labels and images, and loading of pre-inferred masks. 17 | 18 | ## Dependences 19 | 20 | - Qt 6.4.0 21 | 22 | - OpenCV 4.5.5 23 | 24 | - JsonCpp 1.9.5 25 | 26 | - GDAL 3.5.3 27 | 28 | - Eigen 3.4.0 29 | 30 | ## Tutorials and Documents 31 | 32 | - [Download and usage](./docs/Download_and_usage_en.md) 33 | 34 | - [Keyboard shortcuts](./docs/Keyboard_shortcuts_en.md) 35 | - [Usage tutorial](./docs/Usage_tutorial_en.md) 36 | 37 | ## Resources 38 | 39 | - Icon:[SuperMap GIS Product color system function icon library](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=32519&lang=en-us) 40 | - Color:[Color Palette | AntV](https://antv.vision/en/docs/specification/language/palette) 41 | -------------------------------------------------------------------------------- /docs/Download_and_usage.md: -------------------------------------------------------------------------------- 1 | # 下载和使用 2 | 3 | ## 直接使用 4 | 5 | 提供x64版本的Windows exe[下载](https://github.com/geoyee/LabCD/releases/download/0.3/LabCD-0.3-x64-setup.exe),下载后按照流程安装后即可。打包过程由[Inno Setup](https://jrsoftware.org/)提供。 6 | 7 | ## 构建使用 8 | 9 | 1. 克隆项目到本地: 10 | 11 | ```shell 12 | git clone https://github.com/geoyee/LabCD 13 | ``` 14 | 15 | 2. 根据系统下载[Qt 6.4.0](https://www.qt.io/download-qt-installer?hsCtaTracking=99d9dd4f-5681-48d2-b096-470725510d34%7C074ddad0-fdef-4e53-8aa8-5e8a876d6ab4)版本并安装。Windows用户如需要使用Visual Studio需要下载VS扩展:Qt Visual Studio Tools。 16 | 17 | 3. 下载所需的依赖包。目前需要的第三方包有`OpenCV 4.5.5`,`JsonCpp 1.9.5`,`GDAL 3.5.3`和`Eigen 3.4.0`。 18 | 19 | 1. `OpenCV 4.5.5`可从官方github仓库进行[下载](https://github.com/opencv/opencv/releases/tag/4.5.5)并使用cmake进行编译。 20 | 2. `JsonCpp 1.9.5`可从官方github仓库进行[下载](https://github.com/open-source-parsers/jsoncpp/releases/tag/1.9.5)并使用cmake进行编译。 21 | 3. `GDAL 3.5.3`可从官方github仓库进行[下载](https://github.com/OSGeo/gdal/releases/tag/v3.5.3)并使用cmake进行编译。 22 | 4. `Eigen 3.4.0`可从官方gitlab仓库直接进行[下载](https://gitlab.com/libeigen/eigen/-/tree/3.4.0)。 23 | 24 | 准备好之后需要在`labcd`的文件夹下新建`depends`文件夹,其中包括`include`和`lib`文件夹。将Opencv、JsonCpp和GDAL编译完成对应的include和lib文件放入,将Eigen源代码直接放到`depends`下。完成后的`depends`文件夹结构如下: 25 | 26 | ``` 27 | depends 28 | ├-- include 29 | | ├-- gdal 30 | | ├-- json 31 | | └-- opencv2 32 | ├-- lib 33 | | ├-- gdal_i.lib 34 | | ├-- jsoncpp_static.lib 35 | | ├-- jsoncpp_staticd.lib 36 | | ├-- opencv_world455.lib 37 | | └-- opencv_world455d.lib 38 | └-- Eigen 39 | ``` 40 | 41 | 4. 完成后可使用Visual Studio打开对应的`sln`文件构建或其他平台使用Qt Creator打开`pro`文件,建立qmake后构建,即可使用。 42 | 5. CMakeLists由[qmake2cmake](https://www.qt.io/blog/introducing-qmake2cmake)生成,如第三方依赖的路径发生变化,无论是Visual Studio还是Qt Creator或是Cmake都需要对依赖的路径进行调整。 -------------------------------------------------------------------------------- /docs/Download_and_usage_en.md: -------------------------------------------------------------------------------- 1 | # Download and Usage 2 | 3 | ## Direct Use 4 | 5 | The x64 version of Windows exe is available for direct use, which can be downloaded [here](https://github.com/geoyee/LabCD/releases/download/0.3/LabCD-0.3-x64-setup.exe). After downloading, follow the process to install. The packaging process is provided by [Inno Setup](https://jrsoftware.org/). 6 | 7 | ## Building and Use 8 | 9 | 1. Clone the project to your local machine: 10 | 11 | ```shell 12 | git clone https://github.com/geoyee/LabCD 13 | ``` 14 | 15 | 1. Download and install [Qt 6.4.0](https://www.qt.io/download-qt-installer?hsCtaTracking=99d9dd4f-5681-48d2-b096-470725510d34|074ddad0-fdef-4e53-8aa8-5e8a876d6ab4) version according to your system. Windows users who want to use Visual Studio need to download the VS extension: Qt Visual Studio Tools. 16 | 17 | 2. Download the required dependencies. Currently, the required third-party packages are `OpenCV 4.5.5`, `JsonCpp 1.9.5`, `GDAL 3.5.3`, and `Eigen 3.4.0`. 18 | 19 | 1. `OpenCV 4.5.5` can be downloaded from the official GitHub repository [here](https://github.com/opencv/opencv/releases/tag/4.5.5) and compiled using cmake. 20 | 2. `JsonCpp 1.9.5` can be downloaded from the official GitHub repository [here](https://github.com/open-source-parsers/jsoncpp/releases/tag/1.9.5) and compiled using cmake. 21 | 3. `GDAL 3.5.3` can be downloaded from the official GitHub repository [here](https://github.com/OSGeo/gdal/releases/tag/v3.5.3) and compiled using cmake. 22 | 4. `Eigen 3.4.0` can be downloaded directly from the official gitlab repository [here](https://gitlab.com/libeigen/eigen/-/tree/3.4.0). 23 | 24 | After preparing, you need to create a `depends` folder under the `labcd` folder, which includes `include` and `lib` folders. Put the completed include and lib files corresponding to Opencv, JsonCpp, and GDAL into the `depends` folder, and put the Eigen source code directly into `depends`. The `depends` folder structure after completion is as follows: 25 | 26 | ``` 27 | luaCopy codedepends 28 | ├-- include 29 | | ├-- gdal 30 | | ├-- json 31 | | └-- opencv2 32 | ├-- lib 33 | | ├-- gdal_i.lib 34 | | ├-- jsoncpp_static.lib 35 | | ├-- jsoncpp_staticd.lib 36 | | ├-- opencv_world455.lib 37 | | └-- opencv_world455d.lib 38 | └-- Eigen 39 | ``` 40 | 41 | 3. After completion, you can use Visual Studio to open the corresponding `sln` file for building or use Qt Creator to open the `pro` file, establish qmake and build, then you can use it. 42 | 43 | 4. The CMakeLists is generated by [qmake2cmake](https://www.qt.io/blog/introducing-qmake2cmake). If the paths of third-party dependencies change, whether it is Visual Studio, Qt Creator, or CMake, you need to adjust the paths of the dependencies. -------------------------------------------------------------------------------- /docs/Keyboard_shortcuts.md: -------------------------------------------------------------------------------- 1 | # 快捷键列表 2 | 3 | | 操作 | 快捷键 | 4 | | -------------- | ------------------- | 5 | | 画布移动 | 按住鼠标中键移动 | 6 | | 画布缩放 | Ctrl + 鼠标滚轮滚动 | 7 | | 画点 | 鼠标左键 | 8 | | 结束画点 | 鼠标右键 | 9 | | 打开文件夹 | Ctrl + O | 10 | | 切分大图 | Ctrl + B | 11 | | 合并大图 | Ctrl + M | 12 | | 打开快捷键帮助 | Ctrl + H | 13 | | 保存 | Ctrl + S | 14 | | 切换上一张 | S | 15 | | 切换下一张 | F | 16 | | 图像全屏 | Ctrl + F | 17 | | 删除多边形 | Backspace | 18 | | 删除所有多边形 | Delete | -------------------------------------------------------------------------------- /docs/Keyboard_shortcuts_en.md: -------------------------------------------------------------------------------- 1 | # Keyboard shortcut list 2 | 3 | | Operation | Shortcut | 4 | | ------------------------ | --------------------------------- | 5 | | Move canvas | Hold middle mouse button and move | 6 | | Zoom canvas | Ctrl + Scroll mouse wheel | 7 | | Draw point | Left mouse button | 8 | | End point drawing | Right mouse button | 9 | | Open folder | Ctrl + O | 10 | | Split image | Ctrl + B | 11 | | Merge image | Ctrl + M | 12 | | Open shortcut help | Ctrl + H | 13 | | Save | Ctrl + S | 14 | | Switch to previous image | S | 15 | | Switch to next image | F | 16 | | Image fullscreen | Ctrl + F | 17 | | Delete polygon | Backspace | 18 | | Delete all polygons | Delete | -------------------------------------------------------------------------------- /docs/Usage_tutorial.md: -------------------------------------------------------------------------------- 1 | # 使用教程 2 | 3 | 在使用前,需要对文件的组织形式进行了解。当未标注时,文件可只有`A`和`B`两个文件夹,其中保存的两时期影像的文件名相同。文件组织如下: 4 | 5 | ``` 6 | testimgs 7 | ├-- A # 用于存放时段一的图像 8 | | └-- 01.jpg # 时段一图像 9 | ├-- B # 用于存放时段二的图像 10 | | └-- 01.jpg # 时段二图像 11 | ├-- GT # 自动生成,用于存放标注结果 12 | | ├-- 01_pseudo.png # 伪彩色图 13 | | ├-- 01.bmp # 单波段标注图 14 | | └-- 01.json # 自定义格式标注,用于恢复多边形 15 | └-- label.json # 自动生成,用于自动标签导入 16 | ``` 17 | 18 | ## 切分/合并大图 19 | 20 | 在“文件”中使用“切分大图”,需要选择大图路径和切分大小,可将一张大的遥感图像切分为多张小图。其中小图的命名中带有该图在大图像左上角的坐标,因此如果需要使用“合并大图”操作将该文件夹内的小图合并,则不能修改图像的名字。 21 | 22 | ## 清理空白标签 23 | 24 | 在“文件”中使用“清理空白标签”,需要选择符合文件组织的文件夹。该操作会遍历`GT`中的标签,将没有内容的标签对应的两时期影像、json文件等一并删除。 25 | 26 | ## 从标签建立标注 27 | 28 | 在“文件”中使用“从标签建立标注”,需要选择一个仅存放图像掩模的文件夹,该操作会为文件夹中的掩膜生成对应的json文件,使LabCD打开时绘制多边形以用于进行调整。 29 | 30 | ## 变化检测标注 31 | 32 | 核心功能,该功能使用时,在“文件”中使用“打开文件夹”,选择符合文件组织的文件夹,即可进行标注。 33 | 34 | 1. 标注时需要先在“标签列表”中“添加标签”,设置对应的颜色和文字内容。 35 | 2. 可以通过上/下翻页或直接双击“数据列表”中的文件名,切换到需要标注的文件。 36 | 3. 点击一个标签,再在图像中进行标注,鼠标左边新建点,右键完成当前多边形标注。 37 | 4. 完成后切换图像或点击保存都会保存结果,结果包括一个掩膜文件、一个多边形的json文件和一个伪彩色文件。 38 | 39 | *最好每次绘制完一个多边形都在画布上点一下鼠标右键,若突然点击左键无效,可以切换一下标签,重新点击。* 40 | 41 | 其余功能包括: 42 | 43 | - 设置十字丝的颜色。 44 | - 加载一个小图像,该图像为两时期影像计算的变化强度图像。 45 | - 增加第一次加载多光谱数据计算最佳波段组合,后续加载计算所得的波段。 46 | - 删除指定多边形和清空多边形。 47 | - 双击”点“可以删除该点,双击”线“可以在该线处新建一个点。 -------------------------------------------------------------------------------- /docs/Usage_tutorial_en.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | Before using the software, it is necessary to understand the organization of files. When not specified, there may only be two folders, `A` and `B`, containing images from two different periods with the same file name. The file organization is as follows: 4 | 5 | ``` 6 | testimgs 7 | ├-- A # Used to store images from period one 8 | | └-- 01.jpg # Image from period one 9 | ├-- B # Used to store images from period two 10 | | └-- 01.jpg # Image from period two 11 | ├-- GT # Automatically generated folder used to store annotation results 12 | | ├-- 01_pseudo.png # Pseudo-colored image 13 | | ├-- 01.bmp # Single-band annotated image 14 | | └-- 01.json # Custom format annotation file for restoring polygons 15 | └-- label.json # Automatically generated file used for importing auto-labels 16 | ``` 17 | 18 | ## Large image split/merge 19 | 20 | To use the "Large image split" feature under the "Files" menu, select the path of the large image and the splitting size, which can split a large remote sensing image into multiple smaller images. The names of the smaller images contain the coordinates of the upper-left corner of the image in the large image. Therefore, if you need to use the "Large image merge" operation to merge the small images in this folder, you cannot modify the names of the images. 21 | 22 | ## Clear empty mask 23 | 24 | To use the "Clear empty mask" feature under the "Files" menu, select the folder that meets the file organization requirements. This operation will traverse the labels in `GT` and delete the corresponding two-period images, json files, and other files that have no content. 25 | 26 | ## Create Annotation from mask 27 | 28 | To use the "Create Annotation from mask" feature under the "Files" menu, select a folder that contains only image masks. This operation will generate the corresponding json file for the masks in the folder, allowing LabCD to draw polygons for adjustment. 29 | 30 | ## Change Detection Annotation 31 | 32 | The core function of the software is change detection annotation. To use this feature, select a folder that meets the file organization requirements under the "Files" menu, and annotation can be performed. 33 | 34 | 1. To annotate, first "Add Label" in the "Label List" and set the corresponding color and text content. 35 | 2. You can switch to the file that needs to be annotated by paging up/down or directly double-clicking the file name in the "Data List". 36 | 3. Click a label, and then annotate in the image. Left-click to create a new point, and right-click to complete the current polygon annotation. 37 | 4. After completion, switching to another image or clicking "Save" will save the results, including a mask file, a polygon json file, and a pseudo-colored image. 38 | 39 | *It is recommended to right-click the mouse after drawing each polygon on the canvas. If left-clicking suddenly stops working, you can switch to another label and click again.* 40 | 41 | Other features include: 42 | 43 | - Setting the color of the crosshairs. 44 | - Loading a small image, which is the change intensity image calculated from the two periods of images. 45 | - Adding the function to calculate the best band combination for the first loading of multi-spectral data, and loading the calculated bands for subsequent loading. 46 | - Deleting a specified polygon and clearing all polygons. 47 | - Double-clicking a "point" to delete the point, and double-clicking a "line" to create a new point on the line. -------------------------------------------------------------------------------- /labcd/.editorconfig: -------------------------------------------------------------------------------- 1 | # Visual Studio 生成了具有 C++ 设置的 .editorconfig 文件。 2 | root = true 3 | 4 | [*.{c++,cc,cpp,cppm,cu,cuh,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] 5 | 6 | # 文件编码方式 7 | charset = utf-8-bom 8 | 9 | # Visual C++ 代码样式设置 10 | 11 | cpp_generate_documentation_comments = xml 12 | 13 | # Visual C++ 格式设置 14 | 15 | cpp_indent_braces = false 16 | cpp_indent_multi_line_relative_to = innermost_parenthesis 17 | cpp_indent_within_parentheses = indent 18 | cpp_indent_preserve_within_parentheses = true 19 | cpp_indent_case_contents = true 20 | cpp_indent_case_labels = false 21 | cpp_indent_case_contents_when_block = false 22 | cpp_indent_lambda_braces_when_parameter = true 23 | cpp_indent_goto_labels = one_left 24 | cpp_indent_preprocessor = leftmost_column 25 | cpp_indent_access_specifiers = false 26 | cpp_indent_namespace_contents = true 27 | cpp_indent_preserve_comments = false 28 | cpp_new_line_before_open_brace_namespace = ignore 29 | cpp_new_line_before_open_brace_type = ignore 30 | cpp_new_line_before_open_brace_function = ignore 31 | cpp_new_line_before_open_brace_block = ignore 32 | cpp_new_line_before_open_brace_lambda = ignore 33 | cpp_new_line_scope_braces_on_separate_lines = false 34 | cpp_new_line_close_brace_same_line_empty_type = false 35 | cpp_new_line_close_brace_same_line_empty_function = false 36 | cpp_new_line_before_catch = true 37 | cpp_new_line_before_else = true 38 | cpp_new_line_before_while_in_do_while = false 39 | cpp_space_before_function_open_parenthesis = remove 40 | cpp_space_within_parameter_list_parentheses = false 41 | cpp_space_between_empty_parameter_list_parentheses = false 42 | cpp_space_after_keywords_in_control_flow_statements = true 43 | cpp_space_within_control_flow_statement_parentheses = false 44 | cpp_space_before_lambda_open_parenthesis = false 45 | cpp_space_within_cast_parentheses = false 46 | cpp_space_after_cast_close_parenthesis = false 47 | cpp_space_within_expression_parentheses = false 48 | cpp_space_before_block_open_brace = true 49 | cpp_space_between_empty_braces = false 50 | cpp_space_before_initializer_list_open_brace = false 51 | cpp_space_within_initializer_list_braces = true 52 | cpp_space_preserve_in_initializer_list = true 53 | cpp_space_before_open_square_bracket = false 54 | cpp_space_within_square_brackets = false 55 | cpp_space_before_empty_square_brackets = false 56 | cpp_space_between_empty_square_brackets = false 57 | cpp_space_group_square_brackets = true 58 | cpp_space_within_lambda_brackets = false 59 | cpp_space_between_empty_lambda_brackets = false 60 | cpp_space_before_comma = false 61 | cpp_space_after_comma = true 62 | cpp_space_remove_around_member_operators = true 63 | cpp_space_before_inheritance_colon = true 64 | cpp_space_before_constructor_colon = true 65 | cpp_space_remove_before_semicolon = true 66 | cpp_space_after_semicolon = true 67 | cpp_space_remove_around_unary_operator = true 68 | cpp_space_around_binary_operator = insert 69 | cpp_space_around_assignment_operator = insert 70 | cpp_space_pointer_reference_alignment = left 71 | cpp_space_around_ternary_operator = insert 72 | cpp_wrap_preserve_blocks = one_liners 73 | -------------------------------------------------------------------------------- /labcd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(LabCD VERSION 1.0 LANGUAGES CXX) 3 | 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | 6 | find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core) 7 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets) 8 | 9 | qt_standard_project_setup() 10 | 11 | qt_add_executable(LabCD WIN32 MACOSX_BUNDLE 12 | labcd.cpp labcd.h 13 | main.cpp 14 | utils/colormap.cpp utils/colormap.h 15 | utils/fileworker.cpp utils/fileworker.h 16 | utils/imgpress.cpp utils/imgpress.h 17 | utils/label.cpp utils/label.h 18 | widgets/annotationscence.cpp widgets/annotationscence.h 19 | widgets/annotationview.cpp widgets/annotationview.h 20 | widgets/canvas.cpp widgets/canvas.h 21 | widgets/filelist.cpp widgets/filelist.h 22 | widgets/labeltable.cpp widgets/labeltable.h 23 | widgets/labgrid.cpp widgets/labgrid.h 24 | widgets/labline.cpp widgets/labline.h 25 | widgets/labpolygon.cpp widgets/labpolygon.h 26 | widgets/multcanvas.cpp widgets/multcanvas.h 27 | widgets/opttypes.h 28 | ) 29 | target_include_directories(LabCD PRIVATE 30 | depends 31 | depends/include 32 | ) 33 | 34 | target_link_libraries(LabCD PRIVATE 35 | # Remove: L${CMAKE_CURRENT_SOURCE_DIR}/depends/lib/ 36 | Qt::Core 37 | Qt::Gui 38 | Qt::Widgets 39 | gdal_i 40 | ) 41 | 42 | 43 | # Resources: 44 | set(labcd_resource_files 45 | "resources/Icon.png" 46 | ) 47 | 48 | qt_add_resources(LabCD "labcd" 49 | PREFIX 50 | "/main" 51 | FILES 52 | ${labcd_resource_files} 53 | ) 54 | set(labcd1_resource_files 55 | "resources/Chinese.png" 56 | "resources/ClearMask.png" 57 | "resources/Convert.png" 58 | "resources/English.png" 59 | "resources/Folder.png" 60 | "resources/Github.png" 61 | "resources/Help.png" 62 | "resources/Merge.png" 63 | "resources/Split.png" 64 | ) 65 | 66 | qt_add_resources(LabCD "labcd1" 67 | PREFIX 68 | "/menu" 69 | FILES 70 | ${labcd1_resource_files} 71 | ) 72 | set(labcd2_resource_files 73 | "resources/Color.png" 74 | "resources/DeleteAllPolygons.png" 75 | "resources/DeletePolygon.png" 76 | "resources/Enlarge.png" 77 | "resources/Full.png" 78 | "resources/Last.png" 79 | "resources/Narrow.png" 80 | "resources/Next.png" 81 | "resources/Reference.png" 82 | "resources/Save.png" 83 | ) 84 | 85 | qt_add_resources(LabCD "labcd2" 86 | PREFIX 87 | "/tools" 88 | FILES 89 | ${labcd2_resource_files} 90 | ) 91 | set(labcd3_resource_files 92 | "resources/AddLabel.png" 93 | "resources/CantDelete.png" 94 | "resources/Delete.png" 95 | ) 96 | 97 | qt_add_resources(LabCD "labcd3" 98 | PREFIX 99 | "/docks" 100 | FILES 101 | ${labcd3_resource_files} 102 | ) 103 | set(labcd4_resource_files 104 | "configs/colormap.txt" 105 | ) 106 | 107 | qt_add_resources(LabCD "labcd4" 108 | PREFIX 109 | "/configs" 110 | FILES 111 | ${labcd4_resource_files} 112 | ) 113 | set(labcd5_resource_files 114 | "i18n/English.qm" 115 | ) 116 | 117 | qt_add_resources(LabCD "labcd5" 118 | PREFIX 119 | "/translate" 120 | FILES 121 | ${labcd5_resource_files} 122 | ) 123 | 124 | if(WIN32 AND CONFIG(release,debug OR release)) 125 | target_link_libraries(LabCD PRIVATE 126 | # Remove: L${CMAKE_CURRENT_SOURCE_DIR}/depends/lib/ 127 | jsoncpp_static 128 | opencv_world455 129 | ) 130 | endif() 131 | 132 | if((NOT (WIN32 AND CONFIG(release,debug OR release))) AND (WIN32 AND CONFIG(debug,debug OR release))) 133 | target_link_libraries(LabCD PRIVATE 134 | # Remove: L${CMAKE_CURRENT_SOURCE_DIR}/depends/lib/ 135 | jsoncpp_staticd 136 | opencv_world455d 137 | ) 138 | endif() 139 | 140 | if(((NOT (WIN32 AND CONFIG(release,debug OR release))) AND (NOT (WIN32 AND CONFIG(debug,debug OR release)))) AND (UNIX)) 141 | target_link_libraries(LabCD PRIVATE 142 | # Remove: L${CMAKE_CURRENT_SOURCE_DIR}/depends/lib/ 143 | jsoncpp_static 144 | opencv_world455 145 | ) 146 | endif() 147 | 148 | install(TARGETS LabCD 149 | BUNDLE DESTINATION . 150 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 151 | ) 152 | 153 | qt_generate_deploy_app_script( 154 | TARGET LabCD 155 | FILENAME_VARIABLE deploy_script 156 | NO_UNSUPPORTED_PLATFORM_ERROR 157 | ) 158 | install(SCRIPT ${deploy_script}) 159 | -------------------------------------------------------------------------------- /labcd/LabCD.pri: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------- 2 | # This file is generated by the Qt Visual Studio Tools. 3 | # ------------------------------------------------------ 4 | 5 | HEADERS += ./utils/colormap.h \ 6 | ./utils/fileworker.h \ 7 | ./utils/imgpress.h \ 8 | ./utils/label.h \ 9 | ./widgets/labgrid.h \ 10 | ./widgets/labline.h \ 11 | ./widgets/labpolygon.h \ 12 | ./widgets/opttypes.h \ 13 | ./labcd.h \ 14 | ./widgets/annotationscence.h \ 15 | ./widgets/annotationview.h \ 16 | ./widgets/canvas.h \ 17 | ./widgets/multcanvas.h \ 18 | ./widgets/filelist.h \ 19 | ./widgets/labeltable.h 20 | SOURCES += ./utils/colormap.cpp \ 21 | ./utils/fileworker.cpp \ 22 | ./utils/imgpress.cpp \ 23 | ./utils/label.cpp \ 24 | ./widgets/annotationscence.cpp \ 25 | ./widgets/annotationview.cpp \ 26 | ./widgets/canvas.cpp \ 27 | ./widgets/labgrid.cpp \ 28 | ./widgets/labline.cpp \ 29 | ./widgets/labpolygon.cpp \ 30 | ./widgets/multcanvas.cpp \ 31 | ./widgets/filelist.cpp \ 32 | ./widgets/labeltable.cpp \ 33 | ./labcd.cpp \ 34 | ./main.cpp 35 | TRANSLATIONS += ./i18n/English.ts 36 | RESOURCES += labcd.qrc 37 | -------------------------------------------------------------------------------- /labcd/LabCD.pro: -------------------------------------------------------------------------------- 1 | # qt 2 | QT += core gui widgets 3 | TEMPLATE = app 4 | TARGET = LabCD 5 | DESTDIR = ./x64/Release 6 | CONFIG += release 7 | MOC_DIR += GeneratedFiles/$(ConfigurationName) 8 | OBJECTS_DIR += release 9 | UI_DIR += GeneratedFiles 10 | RCC_DIR += GeneratedFiles 11 | include(LabCD.pri) 12 | TRANSLATIONS += i18n/English.ts 13 | 14 | # third-party dependency 15 | unix|win32: LIBS += -L$$PWD/depends/lib/ -lgdal_i 16 | win32:CONFIG(release, debug|release): LIBS += -L$$PWD/depends/lib/ -ljsoncpp_static 17 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/depends/lib/ -ljsoncpp_staticd 18 | else:unix: LIBS += -L$$PWD/depends/lib/ -ljsoncpp_static 19 | win32:CONFIG(release, debug|release): LIBS += -L$$PWD/depends/lib/ -lopencv_world455 20 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/depends/lib/ -lopencv_world455d 21 | else:unix: LIBS += -L$$PWD/depends/lib/ -lopencv_world455 22 | INCLUDEPATH += $$PWD/depends 23 | INCLUDEPATH += $$PWD/depends/include 24 | DEPENDPATH += $$PWD/depends/include 25 | -------------------------------------------------------------------------------- /labcd/LabCD.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.32802.440 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LabCD", "LabCD.vcxproj", "{4714941A-16FB-42A4-B244-878CD5638731}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|x64 = Debug|x64 10 | Release|x64 = Release|x64 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {4714941A-16FB-42A4-B244-878CD5638731}.Debug|x64.ActiveCfg = Debug|x64 14 | {4714941A-16FB-42A4-B244-878CD5638731}.Debug|x64.Build.0 = Debug|x64 15 | {4714941A-16FB-42A4-B244-878CD5638731}.Release|x64.ActiveCfg = Release|x64 16 | {4714941A-16FB-42A4-B244-878CD5638731}.Release|x64.Build.0 = Release|x64 17 | EndGlobalSection 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | GlobalSection(ExtensibilityGlobals) = postSolution 22 | SolutionGuid = {63D7EF12-74CD-4329-8754-3601FEC935BC} 23 | EndGlobalSection 24 | EndGlobal 25 | -------------------------------------------------------------------------------- /labcd/LabCD.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | {4714941A-16FB-42A4-B244-878CD5638731} 15 | QtVS_v304 16 | 10.0.19041.0 17 | 10.0.19041.0 18 | $(MSBuildProjectDirectory)\QtMsBuild 19 | 20 | 21 | 22 | Application 23 | v142 24 | 25 | 26 | Application 27 | v142 28 | 29 | 30 | 31 | 32 | 33 | 34 | 6.4.0_msvc2019_64 35 | core;gui;widgets 36 | debug 37 | 38 | 39 | 6.4.0_msvc2019_64 40 | core;gui;widgets 41 | release 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | $(ProjectDir)depends\include;$(IncludePath) 59 | $(ProjectDir)depends\lib;$(LibraryPath) 60 | 61 | 62 | $(ProjectDir)depends\include;$(IncludePath) 63 | $(ProjectDir)depends\lib;$(LibraryPath) 64 | 65 | 66 | 67 | /NATVIS:"$(IntDir)\qt.natvis" %(AdditionalOptions) 68 | gdal_i.lib;opencv_world455d.lib;jsoncpp_staticd.lib;%(AdditionalDependencies) 69 | 70 | 71 | 72 | 73 | stdc17 74 | $(ProjectDir)depends;%(AdditionalIncludeDirectories) 75 | 76 | 77 | 78 | 79 | /utf-8 /NATVIS:"$(IntDir)\qt.natvis" %(AdditionalOptions) 80 | gdal_i.lib;opencv_world455.lib;jsoncpp_static.lib;%(AdditionalDependencies) 81 | 82 | 83 | 84 | 85 | stdc17 86 | $(ProjectDir)depends;%(AdditionalIncludeDirectories) 87 | 88 | 89 | 90 | 91 | true 92 | true 93 | ProgramDatabase 94 | Disabled 95 | MultiThreadedDebugDLL 96 | 97 | 98 | Windows 99 | true 100 | 101 | 102 | 103 | 104 | true 105 | true 106 | None 107 | MaxSpeed 108 | MultiThreadedDLL 109 | 110 | 111 | Windows 112 | false 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 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /labcd/LabCD.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {99349809-55BA-4b9d-BF79-8FDBB0286EB3} 18 | ui 19 | 20 | 21 | {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C} 22 | ts 23 | 24 | 25 | 26 | 27 | Resource Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | 37 | 38 | Source Files 39 | 40 | 41 | Source Files 42 | 43 | 44 | Source Files 45 | 46 | 47 | Source Files 48 | 49 | 50 | Source Files 51 | 52 | 53 | Source Files 54 | 55 | 56 | Source Files 57 | 58 | 59 | Source Files 60 | 61 | 62 | Source Files 63 | 64 | 65 | Source Files 66 | 67 | 68 | Source Files 69 | 70 | 71 | Source Files 72 | 73 | 74 | Source Files 75 | 76 | 77 | Source Files 78 | 79 | 80 | 81 | 82 | Resource Files 83 | 84 | 85 | Resource Files 86 | 87 | 88 | Resource Files 89 | 90 | 91 | Resource Files 92 | 93 | 94 | Resource Files 95 | 96 | 97 | Resource Files 98 | 99 | 100 | Resource Files 101 | 102 | 103 | Resource Files 104 | 105 | 106 | Resource Files 107 | 108 | 109 | Resource Files 110 | 111 | 112 | Resource Files 113 | 114 | 115 | Resource Files 116 | 117 | 118 | Resource Files 119 | 120 | 121 | Resource Files 122 | 123 | 124 | Resource Files 125 | 126 | 127 | Resource Files 128 | 129 | 130 | Resource Files 131 | 132 | 133 | Resource Files 134 | 135 | 136 | Resource Files 137 | 138 | 139 | Resource Files 140 | 141 | 142 | Resource Files 143 | 144 | 145 | Resource Files 146 | 147 | 148 | Resource Files 149 | 150 | 151 | 152 | 153 | Header Files 154 | 155 | 156 | Header Files 157 | 158 | 159 | Header Files 160 | 161 | 162 | Header Files 163 | 164 | 165 | Header Files 166 | 167 | 168 | Header Files 169 | 170 | 171 | Header Files 172 | 173 | 174 | Header Files 175 | 176 | 177 | 178 | 179 | Header Files 180 | 181 | 182 | Header Files 183 | 184 | 185 | Header Files 186 | 187 | 188 | Header Files 189 | 190 | 191 | Header Files 192 | 193 | 194 | Header Files 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | Translation Files 210 | 211 | 212 | -------------------------------------------------------------------------------- /labcd/configs/colormap.txt: -------------------------------------------------------------------------------- 1 | 244,108,59 2 | 99,102,129 3 | 249,193,0 4 | 160,180,0 5 | 115,82,59 6 | 217,213,180 7 | 51,142,137 8 | 218,147,70 9 | 234,132,163 10 | 61,127,236 11 | 81,202,147 12 | 178,171,245 13 | 42,67,196 14 | 64,158,210 15 | 206,77,194 16 | 45,86,51 17 | 139,140,69 18 | 151,218,229 19 | 135,41,60 20 | 118,33,203 -------------------------------------------------------------------------------- /labcd/i18n/English.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/i18n/English.qm -------------------------------------------------------------------------------- /labcd/i18n/English.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LabCD 6 | 7 | 8 | 文件 9 | File 10 | 11 | 12 | 13 | 打开文件夹 14 | Open image folder 15 | 16 | 17 | 18 | 切分大图 19 | Large image split 20 | 21 | 22 | 23 | 合并大图 24 | Large image merge 25 | 26 | 27 | 28 | 清理空白标签 29 | Clear empty mask 30 | 31 | 32 | 33 | 从标签建立标注 34 | Create annotation from mask 35 | 36 | 37 | 38 | 关于 39 | About 40 | 41 | 42 | 43 | github主页 44 | Homepage of github 45 | 46 | 47 | 48 | 使用帮助 49 | Help 50 | 51 | 52 | 53 | 语言 54 | Language 55 | 56 | 57 | 58 | 59 | 英文 60 | English 61 | 62 | 63 | 64 | 65 | 中文 66 | Chinese 67 | 68 | 69 | 70 | 71 | 当前坐标: 72 | Location: 73 | 74 | 75 | 76 | 数据列表 77 | Data List 78 | 79 | 80 | 81 | 加载图像: 82 | Loaded: 83 | 84 | 85 | 86 | 标签列表 87 | Label List 88 | 89 | 90 | 91 | 当前标签:[ 92 | Label: [ 93 | 94 | 95 | 96 | 保存 97 | Save 98 | 99 | 100 | 101 | 上一张 102 | Last image 103 | 104 | 105 | 106 | 下一张 107 | Next image 108 | 109 | 110 | 111 | 放大 112 | Enlarge 113 | 114 | 115 | 116 | 缩小 117 | Narrow 118 | 119 | 120 | 121 | 全幅缩放 122 | Full 123 | 124 | 125 | 126 | 删除多边形 127 | Delete polygon 128 | 129 | 130 | 131 | 删除所有多边形 132 | Delete all polygons 133 | 134 | 135 | 136 | 137 | 设置十字丝颜色 138 | Set cross color 139 | 140 | 141 | 142 | 打开变化参考图 143 | Open reference image 144 | 145 | 146 | 147 | 光谱变化向量强度参考图 148 | CVA intensity reference 149 | 150 | 151 | 152 | LabCD - 遥感变化检测标注工具 153 | LabCD - Remote sensing change detection annotation tool 154 | 155 | 156 | 157 | 打开大图像 158 | Open large image 159 | 160 | 161 | 162 | 栅格图像文件 (*.tif *.tiff) 163 | Raster File (*.tif *.tiff) 164 | 165 | 166 | 167 | 设置 168 | Setting 169 | 170 | 171 | 172 | 设置切块大小 173 | Set block size 174 | 175 | 176 | 177 | 切分完成,保存至: 178 | Split finished, save to: 179 | 180 | 181 | 182 | 切分失败,可能是不支持的类型或超出范围的切块大小 183 | Split failed, it may be an unsupported type or an out-of-range block size 184 | 185 | 186 | 187 | 合并完成,保存至: 188 | Merge finished, save to: 189 | 190 | 191 | 192 | 合并失败 193 | Merge failed 194 | 195 | 196 | 197 | 清理完成 198 | Clear finished 199 | 200 | 201 | 202 | 转换完成 203 | Convert finished 204 | 205 | 206 | 207 | 保存图像: 208 | Saved: 209 | 210 | 211 | 212 | LabelTable 213 | 214 | 215 | 添加标签 216 | Add label 217 | 218 | 219 | 220 | 标签颜色选择 221 | Label color selection 222 | 223 | 224 | 225 | 背景 226 | Background 227 | 228 | 229 | 230 | MultCanvas 231 | 232 | 233 | 234 | 错误 235 | Error 236 | 237 | 238 | 239 | 无法打开图像文件。 240 | Unable to open image file. 241 | 242 | 243 | 244 | 两个时段的数据大小不一致。 245 | The data size of the two periods is inconsistent. 246 | 247 | 248 | 249 | QObject 250 | 251 | 252 | 253 | 打开图像文件夹 254 | Open image folder 255 | 256 | 257 | 258 | 259 | 错误 260 | Error 261 | 262 | 263 | 264 | 文件组织格式错误,请确保数据文件夹下仅存在两个(或三个)子文件夹,分别为A、B(以及GT)。 265 | The file organization format is incorrect. Please ensure that there are only two (or three) subfolders under the data folder, namely A, B (and GT). 266 | 267 | 268 | 269 | 时段一数据和时段二数据数量不相等。 270 | The number of data in period 1 and period 2 is not equal. 271 | 272 | 273 | 274 | 提示 275 | Infomation 276 | 277 | 278 | 279 | 重启软件后更新语言设置。 280 | Update the language settings after restarting the software. 281 | 282 | 283 | 284 | 打开标签文件夹 285 | Open mask folder 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /labcd/labcd.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "labcd.h" 19 | #include "utils/fileworker.h" 20 | #include "utils/imgpress.h" 21 | #include "widgets/annotationview.h" 22 | 23 | LabCD::LabCD(QWidget* parent) 24 | : QMainWindow(parent) 25 | { 26 | setting = new QSettings("./configs/setting.ini", QSettings::IniFormat); 27 | isCN = setting->value("language").toString() != "EN"; 28 | 29 | /* 状态栏 */ 30 | QStatusBar* lcdStatusBar = statusBar(); 31 | messageState = new QLabel("", this); // 用于显示消息 32 | QLabel* messageLocal = new QLabel("", this); // 用于显示坐标 33 | lcdStatusBar->addWidget(messageState); 34 | lcdStatusBar->addPermanentWidget(messageLocal); 35 | 36 | /* 菜单栏 */ 37 | QMenuBar* lcdMenuBar = menuBar(); 38 | QMenu* fileMenu = new QMenu(tr("文件"), this); 39 | QAction* opensAct = fileMenu->addAction( 40 | QIcon(":/menu/resources/Folder.png"), tr("打开文件夹")); 41 | opensAct->setShortcut(QKeySequence("Ctrl+O")); 42 | connect(opensAct, &QAction::triggered, this, &LabCD::openDir); 43 | fileMenu->addSeparator(); 44 | QAction* splitAct = fileMenu->addAction( 45 | QIcon(":/menu/resources/Split.png"), tr("切分大图")); 46 | splitAct->setShortcut(QKeySequence("Ctrl+B")); 47 | connect(splitAct, &QAction::triggered, this, &LabCD::openBigImageFile); 48 | QAction* mergeAct = fileMenu->addAction( 49 | QIcon(":/menu/resources/Merge.png"), tr("合并大图")); 50 | mergeAct->setShortcut(QKeySequence("Ctrl+M")); 51 | connect(mergeAct, &QAction::triggered, this, &LabCD::mergeBigImage); 52 | QAction* clearAct = fileMenu->addAction( 53 | QIcon(":/menu/resources/ClearMask.png"), tr("清理空白标签")); 54 | connect(clearAct, &QAction::triggered, this, &LabCD::clearEmptyMask); 55 | QAction* convertAct = fileMenu->addAction( 56 | QIcon(":/menu/resources/Convert.png"), tr("从标签建立标注")); 57 | connect(convertAct, &QAction::triggered, this, &LabCD::convertMask2Json); 58 | lcdMenuBar->addMenu(fileMenu); 59 | QMenu* aboutMenu = new QMenu(tr("关于"), this); 60 | QAction* githubAct = aboutMenu->addAction( 61 | QIcon(":/menu/resources/Github.png"), tr("github主页")); 62 | connect(githubAct, &QAction::triggered, [=]() { 63 | QDesktopServices::openUrl(QUrl("https://github.com/geoyee/LabCD")); 64 | }); 65 | QAction* helpAct = aboutMenu->addAction( 66 | QIcon(":/menu/resources/Help.png"), tr("使用帮助")); 67 | connect(helpAct, &QAction::triggered, [=]() { 68 | QString tutorial = "https://github.com/geoyee/LabCD/tree/develop/docs/Usage_tutorial.md"; 69 | if (!isCN) 70 | tutorial = "https://github.com/geoyee/LabCD/tree/develop/docs/Usage_tutorial_en.md"; 71 | QDesktopServices::openUrl(QUrl(tutorial)); 72 | }); 73 | helpAct->setShortcut(QKeySequence("Ctrl+H")); 74 | lcdMenuBar->addMenu(aboutMenu); 75 | QMenu* languMenu = new QMenu(tr("语言"), this); 76 | QAction* setLangeAct; 77 | if (isCN) 78 | setLangeAct = languMenu->addAction( 79 | QIcon(":/menu/resources/English.png"), tr("英文")); 80 | else 81 | setLangeAct = languMenu->addAction( 82 | QIcon(":/menu/resources/Chinese.png"), tr("中文")); 83 | connect(setLangeAct, &QAction::triggered, [=]() { 84 | if (isCN) 85 | { 86 | setLangeAct->setIcon(QIcon(":/menu/resources/Chinese.png")); 87 | setLangeAct->setText(tr("中文")); 88 | setting->setValue("language", "EN"); 89 | } 90 | else 91 | { 92 | setLangeAct->setIcon(QIcon(":/menu/resources/English.png")); 93 | setLangeAct->setText(tr("英文")); 94 | setting->setValue("language", "CN"); 95 | } 96 | isCN = !isCN; 97 | QMessageBox::information( 98 | parent, 99 | QObject::tr("提示"), 100 | QObject::tr("重启软件后更新语言设置。") 101 | ); 102 | }); 103 | lcdMenuBar->addMenu(languMenu); 104 | 105 | /* 绘图界面 */ 106 | drawCanvas = new MultCanvas(this); 107 | connect(drawCanvas->t1Canva->aView, &AnnotationView::mousePosChanged, 108 | [=](double x, double y) { 109 | messageLocal->setText( 110 | tr("当前坐标:") + \ 111 | QString::fromStdString(std::to_string(x)) + ", " + \ 112 | QString::fromStdString(std::to_string(y))); 113 | }); 114 | connect(drawCanvas->t2Canva->aView, &AnnotationView::mousePosChanged, 115 | [=](double x, double y) { 116 | messageLocal->setText( 117 | tr("当前坐标:") + \ 118 | QString::fromStdString(std::to_string(x)) + ", " + \ 119 | QString::fromStdString(std::to_string(y))); 120 | }); 121 | setCentralWidget(drawCanvas); 122 | 123 | /* 图像文件列表 */ 124 | QDockWidget* filesDock = new QDockWidget(tr("数据列表"), this); 125 | filesDock->setMinimumWidth(200); 126 | filesDock->setAllowedAreas(Qt::RightDockWidgetArea); 127 | fListWidget = new FileList(this); 128 | // 保存图像 129 | connect(fListWidget, &FileList::saveLastFileRequest, this, &LabCD::save); 130 | // 加载图像 131 | connect(fListWidget, &FileList::FileClickRequest, 132 | [=](QString t1Path, QString t2Path, QString jsonPath) { 133 | drawCanvas->loadImages(t1Path, t2Path, jsonPath); 134 | updatePolysColor(); 135 | QFileInfo fileInfo(t1Path); 136 | fileName = fileInfo.fileName(); 137 | messageState->setText(tr("加载图像:") + t1Path); 138 | }); 139 | filesDock->setWidget(fListWidget); 140 | addDockWidget(Qt::RightDockWidgetArea, filesDock); 141 | 142 | /* 标签列表 */ 143 | QDockWidget* labelsDock = new QDockWidget(tr("标签列表"), this); 144 | labelsDock->setMinimumWidth(200); 145 | labelsDock->setAllowedAreas(Qt::RightDockWidgetArea); 146 | labTableWidget = new LabelTable(this); 147 | labelsDock->setWidget(labTableWidget); 148 | connect(labTableWidget, &LabelTable::labelSelected, [=](Label* nowLabel) { 149 | drawCanvas->labelSelected(nowLabel); 150 | messageState->setText(tr("当前标签:[") + \ 151 | QString::fromStdString(std::to_string(nowLabel->getIndex())) + \ 152 | "] " + nowLabel->getName()); 153 | }); 154 | connect(labTableWidget, &LabelTable::colorChanged, 155 | [=](int labelIndex, QColor newColor) { 156 | // 更新界面上的多边形颜色 157 | for (int i = 0; i < drawCanvas->t1Canva->aScene->polygonItems.count(); ++i) 158 | { 159 | if (drawCanvas->t1Canva->aScene->polygonItems[i]->labelIndex == \ 160 | labelIndex) 161 | { 162 | drawCanvas->t1Canva->aScene->polygonItems[i]->setColor( 163 | newColor, newColor); 164 | drawCanvas->t2Canva->aScene->polygonItems[i]->setColor( 165 | newColor, newColor); 166 | } 167 | drawCanvas->t1Canva->aScene->setColor(newColor, newColor); 168 | drawCanvas->t2Canva->aScene->setColor(newColor, newColor); 169 | } 170 | }); 171 | // 同步Json的加载 172 | connect(drawCanvas->t1Canva, &Canvas::addJsonPoly, \ 173 | labTableWidget, &LabelTable::changeLabelDuotoAddPolyJson); 174 | addDockWidget(Qt::RightDockWidgetArea, labelsDock); 175 | 176 | /* 工具栏 */ 177 | QToolBar* lcdToolBar = new QToolBar(this); 178 | QAction* saveAct = lcdToolBar->addAction( 179 | QIcon(":/tools/resources/Save.png"), tr("保存")); 180 | saveAct->setShortcut(QKeySequence("Ctrl+S")); 181 | connect(saveAct, &QAction::triggered, this, &LabCD::save); 182 | lcdToolBar->addSeparator(); 183 | QAction* lastAct = lcdToolBar->addAction( 184 | QIcon(":/tools/resources/Last.png"), tr("上一张")); 185 | connect(lastAct, &QAction::triggered, [=]() { 186 | save(); 187 | fListWidget->gotoLastItem(); 188 | }); 189 | lastAct->setShortcut(QKeySequence("S")); 190 | QAction* nextAct = lcdToolBar->addAction( 191 | QIcon(":/tools/resources/Next.png"), tr("下一张")); 192 | nextAct->setShortcut(QKeySequence("F")); 193 | connect(nextAct, &QAction::triggered, [=]() { 194 | save(); 195 | fListWidget->gotoNextItem(); 196 | }); 197 | lcdToolBar->addSeparator(); 198 | QAction* enlargeAct = lcdToolBar->addAction( 199 | QIcon(":/tools/resources/Enlarge.png"), tr("放大")); 200 | connect(enlargeAct, &QAction::triggered, [=]() { 201 | drawCanvas->t1Canva->aView->scaleZoom(1.1); // 自动同步t2 202 | }); 203 | QAction* narrowAct = lcdToolBar->addAction( 204 | QIcon(":/tools/resources/Narrow.png"), tr("缩小")); 205 | connect(narrowAct, &QAction::triggered, [=]() { 206 | drawCanvas->t1Canva->aView->scaleZoom(0.9); // 自动同步t2 207 | }); 208 | QAction* fullAct = lcdToolBar->addAction( 209 | QIcon(":/tools/resources/Full.png"), tr("全幅缩放")); 210 | fullAct->setShortcut(QKeySequence("Ctrl+F")); 211 | connect(fullAct, &QAction::triggered, [=]() { 212 | if (drawCanvas->imageWidth != 0 && drawCanvas->imageHeight != 0) 213 | drawCanvas->t1Canva->resetZoom( 214 | drawCanvas->imageWidth, drawCanvas->imageHeight); // 自动同步t2 215 | }); 216 | lcdToolBar->addSeparator(); 217 | QAction* delPolyAct = lcdToolBar->addAction( 218 | QIcon(":/tools/resources/DeletePolygon.png"), tr("删除多边形")); 219 | connect(delPolyAct, &QAction::triggered, [=]() { 220 | int f1Index = drawCanvas->t1Canva->aScene->findFocusPolygon(); 221 | int f2Index = drawCanvas->t2Canva->aScene->findFocusPolygon(); 222 | int delIndex = f1Index > f2Index ? f1Index : f2Index; 223 | drawCanvas->t1Canva->aScene->delPoly(delIndex); 224 | drawCanvas->t2Canva->aScene->delPoly(delIndex); 225 | }); 226 | delPolyAct->setShortcut(QKeySequence("Backspace")); 227 | QAction* delAllPolysAct = lcdToolBar->addAction( 228 | QIcon(":/tools/resources/DeleteAllPolygons.png"), tr("删除所有多边形")); 229 | connect(delAllPolysAct, &QAction::triggered, [=]() { 230 | drawCanvas->t1Canva->aScene->removeAllPolygons(); 231 | drawCanvas->t2Canva->aScene->removeAllPolygons(); 232 | }); 233 | delAllPolysAct->setShortcut(QKeySequence("Delete")); 234 | lcdToolBar->addSeparator(); 235 | QAction* crossColorAct = lcdToolBar->addAction( 236 | QIcon(":/tools/resources/Color.png"), tr("设置十字丝颜色")); 237 | connect(crossColorAct, &QAction::triggered, this, &LabCD::setCrossPenColor); 238 | lcdToolBar->addSeparator(); 239 | QAction* isCVAAct = lcdToolBar->addAction( 240 | QIcon(":/tools/resources/Reference.png"), tr("打开变化参考图")); 241 | isCVAAct->setCheckable(true); 242 | // 完成 243 | lcdToolBar->setMovable(false); 244 | addToolBar(Qt::LeftToolBarArea, lcdToolBar); 245 | 246 | /* 变化参考图 */ 247 | QDockWidget* refDock = new QDockWidget(tr("光谱变化向量强度参考图"), this); 248 | refDock->setAllowedAreas(Qt::NoDockWidgetArea); 249 | QLabel* imgRef = new QLabel(this); 250 | refDock->setFloating(true); 251 | refDock->hide(); 252 | connect(drawCanvas, &MultCanvas::addimgDiff, [=](cv::Mat imgDiff) { 253 | if (isCVAAct->isChecked()) 254 | { 255 | refNewHeight = refNewWidth * imgDiff.rows / imgDiff.cols; 256 | cv::cvtColor(imgDiff, imgDiff, cv::COLOR_RGB2BGR); 257 | cv::resize(imgDiff, imgDiff, cv::Size(refNewWidth, refNewHeight)); 258 | QImage qimg = QImage( 259 | (const uchar*)(imgDiff.data), imgDiff.cols, imgDiff.rows, 260 | imgDiff.cols * imgDiff.channels(), QImage::Format_RGB888 261 | ); 262 | imgRef->setPixmap(QPixmap::fromImage(qimg)); 263 | refDock->setFixedSize(refNewWidth, refNewHeight); 264 | refDock->show(); 265 | } 266 | }); 267 | refDock->setWidget(imgRef); 268 | addDockWidget(Qt::NoDockWidgetArea, refDock); 269 | 270 | /* 界面设置 */ 271 | resize(1200, 600); 272 | setWindowTitle(tr("LabCD - 遥感变化检测标注工具")); 273 | setWindowIcon(QIcon(":/main/resources/Icon.png")); 274 | } 275 | 276 | LabCD::~LabCD() 277 | { 278 | 279 | } 280 | 281 | void LabCD::openDir() 282 | { 283 | QStringList t1List; 284 | QStringList t2List; 285 | if (FileWorker::openImageDir(&t1List, &t2List, nullptr, this)) 286 | { 287 | // 新建保存目录 288 | QFileInfo fileInfo(t1List.at(0)); 289 | savePath = fileInfo.path(); 290 | savePath = savePath.replace("\\", "/"); 291 | savePath = savePath.section("/", 0, -2); 292 | QString saveImgPath = savePath + "/GT"; 293 | FileWorker::createFolder(saveImgPath); 294 | // 加载已有标签 295 | QString jsonPath = savePath + "/label.json"; 296 | QFileInfo jsonFileInfo(jsonPath); 297 | if (jsonFileInfo.isFile()) 298 | labTableWidget->importLabelFromFile(jsonPath); 299 | // 加载图像 300 | fListWidget->addFileNames(t1List, t2List); 301 | fListWidget->gotoItem(0); 302 | // 加载总进度 303 | fListWidget->resetProgress(); 304 | } 305 | } 306 | 307 | void LabCD::openBigImageFile() 308 | { 309 | // 获取文件路径 310 | QString fileName = QFileDialog::getOpenFileName( 311 | this, 312 | tr("打开大图像"), 313 | QString(), 314 | tr("栅格图像文件 (*.tif *.tiff)") 315 | ); 316 | if (fileName == "") 317 | return; 318 | QString saveDir = QFileInfo(fileName).absolutePath() + \ 319 | QDir::separator() + "split_output"; 320 | saveDir = saveDir.replace("\\", "/"); 321 | FileWorker::createFolder(saveDir); 322 | // 获取切分大小 323 | bool blockOk = false; 324 | int blockSize = QInputDialog::getInt( 325 | this, 326 | tr("设置"), 327 | tr("设置切块大小"), 328 | 512, 1, 2048, 1, 329 | &blockOk 330 | ); 331 | if (!blockOk) 332 | return; 333 | QtConcurrent::run([=]() { 334 | if (ImagePress::splitTiff(fileName, saveDir, blockSize, blockSize)) 335 | messageState->setText(tr("切分完成,保存至:") + saveDir); 336 | else 337 | messageState->setText(tr("切分失败,可能是不支持的类型或超出范围的切块大小")); 338 | }); 339 | } 340 | 341 | void LabCD::mergeBigImage() 342 | { 343 | QString dirPath = QFileDialog::getExistingDirectory( 344 | this, 345 | QObject::tr("打开图像文件夹"), 346 | QString(), 347 | QFileDialog::ShowDirsOnly 348 | ); 349 | if (dirPath.isEmpty()) 350 | return; 351 | QtConcurrent::run([=]() { 352 | if (ImagePress::mergeTiff(dirPath)) 353 | messageState->setText( 354 | tr("合并完成,保存至:") + dirPath + "/merge.tif"); 355 | else 356 | messageState->setText(tr("合并失败")); 357 | }); 358 | } 359 | 360 | void LabCD::clearEmptyMask() 361 | { 362 | QStringList t1List; 363 | QStringList t2List; 364 | QStringList GTList; 365 | if (FileWorker::openImageDir(&t1List, &t2List, >List, this)) 366 | { 367 | QtConcurrent::run([=]() { 368 | LabCD::_clearEmptyMask(t1List, t2List, GTList); }); 369 | messageState->setText(tr("清理完成")); 370 | } 371 | } 372 | 373 | void LabCD::_clearEmptyMask( 374 | QStringList t1List, QStringList t2List, QStringList GTList) 375 | { 376 | std::sort(t1List.begin(), t1List.end()); 377 | std::sort(t2List.begin(), t2List.end()); 378 | std::sort(GTList.begin(), GTList.end()); 379 | QFileInfo fInfo; 380 | QString pathName; 381 | for (int i = 0; i < GTList.size(); ++i) 382 | { 383 | if (ImagePress::maskIsEmpty(GTList.at(i))) 384 | { 385 | // 清理图像 386 | QFile::remove(t1List.at(i)); 387 | QFile::remove(t2List.at(i)); 388 | // 清理标签 389 | fInfo = QFileInfo(GTList.at(i)); 390 | pathName = fInfo.path() + "/" + fInfo.baseName(); 391 | QFile::remove(GTList.at(i)); 392 | QFile::remove(pathName + ".json"); 393 | QFile::remove(pathName + "_pseudo.png"); 394 | } 395 | } 396 | } 397 | 398 | void LabCD::convertMask2Json() 399 | { 400 | QString dirPath = QFileDialog::getExistingDirectory( 401 | this, 402 | QObject::tr("打开标签文件夹"), 403 | QString(), 404 | QFileDialog::ShowDirsOnly 405 | ); 406 | if (dirPath.isEmpty()) 407 | return; 408 | QtConcurrent::run([=]() { LabCD::_convertMask2Json(dirPath); }); 409 | messageState->setText(tr("转换完成")); 410 | } 411 | 412 | void LabCD::_convertMask2Json(QString dirPath) 413 | { 414 | QDir maskDir(dirPath); 415 | QStringList nameFilters; 416 | nameFilters << "*.jpg" << "*.jpeg" << "*.png" << "*.tif" << "*.tiff"; 417 | QStringList maskList = (maskDir).entryList( 418 | nameFilters, QDir::Readable | QDir::Files, QDir::Name); 419 | QString maskPath; 420 | for (int i = 0; i < maskList.size(); ++i) 421 | { 422 | maskPath = dirPath + "/" + maskList.at(i); 423 | ImagePress::savePolygonFromMask(maskPath); 424 | } 425 | } 426 | 427 | void LabCD::save() 428 | { 429 | if (drawCanvas->imageWidth != 0 && drawCanvas->imageHeight != 0) 430 | { 431 | QString saveImgPath = savePath + "/GT/" + fileName; 432 | int labNum = labTableWidget->getLen(); 433 | drawCanvas->finished(); 434 | ImagePress::saveResultFromPolygon( 435 | saveImgPath, 436 | labNum, 437 | drawCanvas->imageHeight, 438 | drawCanvas->imageWidth, 439 | drawCanvas->t1Canva->aScene->polygonItems, 440 | drawCanvas->projs, 441 | drawCanvas->trans 442 | ); 443 | fListWidget->finishedCurrentItem(); 444 | messageState->setText(tr("保存图像:") + saveImgPath); 445 | fListWidget->progressUpAdd(); 446 | } 447 | } 448 | 449 | void LabCD::setCrossPenColor() 450 | { 451 | QColor nowCrossColor = drawCanvas->getCrossPenColor(); 452 | QColor color = QColorDialog::getColor( 453 | nowCrossColor, this, tr("设置十字丝颜色"), QColorDialog::ShowAlphaChannel 454 | ); 455 | setting->setValue("cross_color", color); 456 | drawCanvas->setCrossPenColor(color); 457 | } 458 | 459 | void LabCD::updatePolysColor() 460 | { 461 | for (int i = 0; i < drawCanvas->t1Canva->aScene->polygonItems.count(); ++i) 462 | { 463 | int idx = drawCanvas->t1Canva->aScene->polygonItems[i]->getLabelIndex(); 464 | QColor polyColor = drawCanvas->t1Canva->aScene->polygonItems[i]->getColor(); 465 | QColor labColor = labTableWidget->getColorByIndex(idx); 466 | if (polyColor.rgb() != labColor.rgb()) 467 | { 468 | drawCanvas->t1Canva->aScene->polygonItems[i]->setColor( 469 | labColor, labColor); 470 | drawCanvas->t2Canva->aScene->polygonItems[i]->setColor( 471 | labColor, labColor); 472 | } 473 | } 474 | } 475 | 476 | void LabCD::closeEvent(QCloseEvent* ev) 477 | { 478 | // 保存标签 479 | if (savePath != "") 480 | { 481 | QString jsonPath = savePath + "/label.json"; 482 | labTableWidget->exportLabelToFile(jsonPath); 483 | } 484 | // 保存界面 485 | setting->setValue("layout_status", QByteArray(saveState())); 486 | QMainWindow::closeEvent(ev); 487 | } 488 | -------------------------------------------------------------------------------- /labcd/labcd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "widgets/labeltable.h" 7 | #include "widgets/filelist.h" 8 | #include "widgets/multcanvas.h" 9 | 10 | class LabCD : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | private: 15 | QSettings* setting; 16 | QString savePath = ""; 17 | QString fileName; 18 | bool isCN; 19 | const int refNewWidth = 200; 20 | int refNewHeight = 200; 21 | 22 | void openDir(); 23 | void openBigImageFile(); 24 | void mergeBigImage(); 25 | void clearEmptyMask(); 26 | inline void _clearEmptyMask( 27 | QStringList t1List, QStringList t2List, QStringList GTList); 28 | void convertMask2Json(); 29 | inline void _convertMask2Json(QString dirPath); 30 | void save(); 31 | void setCrossPenColor(); 32 | void updatePolysColor(); 33 | 34 | public: 35 | FileList* fListWidget = nullptr; // 数据列表 36 | LabelTable* labTableWidget = nullptr; // 标签列表 37 | MultCanvas* drawCanvas = nullptr; // 绘图界面 38 | QLabel* messageState = nullptr; // 消息框 39 | 40 | LabCD(QWidget* parent = nullptr); 41 | ~LabCD(); 42 | void closeEvent(QCloseEvent* ev); 43 | }; 44 | -------------------------------------------------------------------------------- /labcd/labcd.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/Icon.png 4 | 5 | 6 | resources/Folder.png 7 | resources/Github.png 8 | resources/Help.png 9 | resources/Chinese.png 10 | resources/English.png 11 | resources/Split.png 12 | resources/Merge.png 13 | resources/ClearMask.png 14 | resources/Convert.png 15 | 16 | 17 | resources/Enlarge.png 18 | resources/Full.png 19 | resources/Narrow.png 20 | resources/Last.png 21 | resources/Next.png 22 | resources/Save.png 23 | resources/DeleteAllPolygons.png 24 | resources/DeletePolygon.png 25 | resources/Color.png 26 | resources/Reference.png 27 | 28 | 29 | resources/AddLabel.png 30 | resources/Delete.png 31 | resources/CantDelete.png 32 | 33 | 34 | configs/colormap.txt 35 | 36 | 37 | i18n/English.qm 38 | 39 | 40 | -------------------------------------------------------------------------------- /labcd/main.cpp: -------------------------------------------------------------------------------- 1 | #include "labcd.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include // debug 7 | 8 | int main(int argc, char* argv[]) 9 | { 10 | QApplication a(argc, argv); 11 | QSettings setting("./configs/setting.ini", QSettings::IniFormat); 12 | // 居中显示 13 | QScreen* scr = a.primaryScreen(); 14 | int scr_w = scr->size().width(); 15 | int scr_h = scr->size().height(); 16 | // 加载语言 17 | QString langu = setting.value("language").toString(); 18 | QTranslator translator; 19 | if (langu == "EN") 20 | { 21 | translator.load(":/translate/i18n/English.qm"); 22 | a.installTranslator(&translator); 23 | } 24 | else 25 | a.removeTranslator(&translator); 26 | // 重加载界面和设置 27 | LabCD w; 28 | w.drawCanvas->setCrossPenColor(setting.value("cross_color").value()); 29 | QByteArray layoutStatus = setting.value("layout_status").toByteArray(); 30 | w.restoreState(layoutStatus); 31 | w.move((scr_w - w.width()) / 2, (scr_h - w.height()) / 2); 32 | w.show(); 33 | return a.exec(); 34 | } 35 | -------------------------------------------------------------------------------- /labcd/resources/AddLabel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/AddLabel.png -------------------------------------------------------------------------------- /labcd/resources/CantDelete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/CantDelete.png -------------------------------------------------------------------------------- /labcd/resources/Chinese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Chinese.png -------------------------------------------------------------------------------- /labcd/resources/ClearMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/ClearMask.png -------------------------------------------------------------------------------- /labcd/resources/Color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Color.png -------------------------------------------------------------------------------- /labcd/resources/Convert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Convert.png -------------------------------------------------------------------------------- /labcd/resources/Delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Delete.png -------------------------------------------------------------------------------- /labcd/resources/DeleteAllPolygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/DeleteAllPolygons.png -------------------------------------------------------------------------------- /labcd/resources/DeletePolygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/DeletePolygon.png -------------------------------------------------------------------------------- /labcd/resources/English.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/English.png -------------------------------------------------------------------------------- /labcd/resources/Enlarge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Enlarge.png -------------------------------------------------------------------------------- /labcd/resources/Folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Folder.png -------------------------------------------------------------------------------- /labcd/resources/Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Full.png -------------------------------------------------------------------------------- /labcd/resources/Github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Github.png -------------------------------------------------------------------------------- /labcd/resources/Help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Help.png -------------------------------------------------------------------------------- /labcd/resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Icon.png -------------------------------------------------------------------------------- /labcd/resources/Last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Last.png -------------------------------------------------------------------------------- /labcd/resources/Merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Merge.png -------------------------------------------------------------------------------- /labcd/resources/Narrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Narrow.png -------------------------------------------------------------------------------- /labcd/resources/Next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Next.png -------------------------------------------------------------------------------- /labcd/resources/Reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Reference.png -------------------------------------------------------------------------------- /labcd/resources/Save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Save.png -------------------------------------------------------------------------------- /labcd/resources/Split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoyee/LabCD/88e0b4080a96109d41db53ff647b1768d621fa20/labcd/resources/Split.png -------------------------------------------------------------------------------- /labcd/utils/colormap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "colormap.h" 3 | 4 | int* ColorMap::strToColor(std::string& str) 5 | { 6 | int* color = new int[3]; 7 | std::size_t startIdx = 0; 8 | std::size_t endIdx = 0; 9 | std::string strs = str + ","; 10 | for (int i = 0; i < 3; i++) 11 | { 12 | endIdx = strs.find(",", startIdx); 13 | std::string cItem = strs.substr(startIdx, endIdx - startIdx); 14 | startIdx = endIdx + 1; 15 | color[i] = atoi(cItem.c_str()); 16 | } 17 | return color; 18 | } 19 | 20 | ColorMap::ColorMap() 21 | { 22 | index = 0; 23 | QFile file(":/configs/configs/colormap.txt"); 24 | file.open(QIODevice::ReadOnly); 25 | while (!file.atEnd()) 26 | { 27 | QByteArray data = file.readLine(); 28 | std::string buff = QString(data).toStdString(); 29 | int* c = ColorMap::strToColor(buff); 30 | colorList.push_back(c); 31 | } 32 | file.close(); 33 | } 34 | 35 | ColorMap::~ColorMap() 36 | { 37 | 38 | } 39 | 40 | void ColorMap::setIndex(int _index) 41 | { 42 | index = _index; 43 | } 44 | 45 | QColor ColorMap::getColor() 46 | { 47 | if (index >= colorList.size()) 48 | index = 0; 49 | int* c = colorList[index]; 50 | index++; 51 | return QColor(c[0], c[1], c[2]); 52 | } 53 | 54 | QColor ColorMap::getColor(int index) 55 | { 56 | if (index >= colorList.size()) 57 | index = 0; 58 | int* c = colorList[index]; 59 | return QColor(c[0], c[1], c[2]); 60 | } 61 | -------------------------------------------------------------------------------- /labcd/utils/colormap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class ColorMap 9 | { 10 | private: 11 | int index; 12 | std::vector colorList; 13 | 14 | int* strToColor(std::string& str); 15 | 16 | public: 17 | ColorMap(); 18 | ~ColorMap(); 19 | void setIndex(int _index); 20 | QColor getColor(); 21 | QColor getColor(int index); 22 | }; 23 | -------------------------------------------------------------------------------- /labcd/utils/fileworker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "fileworker.h" 5 | 6 | bool FileWorker::openImageDir( 7 | QStringList* t1List, 8 | QStringList* t2List, 9 | QStringList* GTList, 10 | QWidget* parent 11 | ) 12 | { 13 | QString dirPath = QFileDialog::getExistingDirectory( 14 | parent, 15 | QObject::tr("打开图像文件夹"), 16 | QString(), 17 | QFileDialog::ShowDirsOnly 18 | ); 19 | if (dirPath.isEmpty()) 20 | return false; 21 | // 开始读取 22 | QDir dir(dirPath); 23 | QStringList subDirList; 24 | subDirList = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); 25 | if ((subDirList.size() != 2 && subDirList.size() != 3) || 26 | (subDirList.size() == 2 && subDirList.at(0) != "A" && subDirList.at(1) != "B") || 27 | (subDirList.size() == 3 && subDirList.at(0) != "A" && subDirList.at(1) != "B" && subDirList.at(2) != "GT")) 28 | { 29 | QMessageBox::critical( 30 | parent, 31 | QObject::tr("错误"), 32 | QObject::tr("文件组织格式错误,请确保数据文件夹下仅存在两个(或三个)子文件夹,分别为A、B(以及GT)。") 33 | ); 34 | return false; 35 | } 36 | else // 获取所有图像 37 | { 38 | QStringList nameFilters; 39 | nameFilters << "*.jpg" << "*.jpeg" << "*.png" << "*.tif" << "*.tiff"; 40 | QDir dirT1(dirPath + "/" + subDirList.at(0)); 41 | QStringList t1ListTmp = (dirT1).entryList( 42 | nameFilters, QDir::Readable | QDir::Files, QDir::Name); 43 | QDir dirT2(dirPath + "/" + subDirList.at(1)); 44 | QStringList t2ListTmp = (dirT2).entryList( 45 | nameFilters, QDir::Readable | QDir::Files, QDir::Name); 46 | if (t1ListTmp.size() != t2ListTmp.size()) 47 | { 48 | QMessageBox::critical( 49 | parent, 50 | QObject::tr("错误"), 51 | QObject::tr("时段一数据和时段二数据数量不相等。") 52 | ); 53 | return false; 54 | } 55 | if (GTList != nullptr && subDirList.size() == 3) 56 | { 57 | QDir dirGT(dirPath + "/" + subDirList.at(2)); 58 | QStringList GTListTmp = (dirGT).entryList( 59 | nameFilters, QDir::Readable | QDir::Files, QDir::Name); 60 | QString name; 61 | QStringList nameFlag; 62 | for (int i = 0; i < GTListTmp.size(); ++i) 63 | { 64 | name = GTListTmp.at(i); 65 | nameFlag = name.split("_"); 66 | if (nameFlag[nameFlag.size() - 1] == "pseudo.png") 67 | continue; 68 | GTList->append( 69 | dirPath + "/" + subDirList.at(2) + "/" + name); 70 | } 71 | } 72 | for (int i = 0; i < t1ListTmp.size(); ++i) 73 | { 74 | t1List->append( 75 | dirPath + "/" + subDirList.at(0) + "/" + t1ListTmp.at(i)); 76 | t2List->append( 77 | dirPath + "/" + subDirList.at(1) + "/" + t2ListTmp.at(i)); 78 | } 79 | return true; 80 | } 81 | } 82 | 83 | bool FileWorker::createFolder(QString path) 84 | { 85 | QDir* folder = new QDir(path); 86 | if (!folder->exists()) 87 | { 88 | if (folder->mkpath(path)) 89 | return true; 90 | } 91 | return true; 92 | } 93 | -------------------------------------------------------------------------------- /labcd/utils/fileworker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class FileWorker 6 | { 7 | public: 8 | static bool openImageDir( 9 | QStringList* t1List, 10 | QStringList* t2List, 11 | QStringList* GTList = nullptr, 12 | QWidget* parent = nullptr 13 | ); 14 | 15 | static bool createFolder(QString path); 16 | }; 17 | -------------------------------------------------------------------------------- /labcd/utils/imgpress.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "colormap.h" 9 | #include "imgpress.h" 10 | 11 | bool ImagePress::createArr( 12 | void** data, GDALDataType types, int xSize, int ySize, int band) 13 | { 14 | switch (types) 15 | { 16 | case GDT_Byte: 17 | *data = new unsigned char[xSize * ySize * band]; 18 | break; 19 | case GDT_UInt16: 20 | *data = new unsigned short[xSize * ySize * band]; 21 | break; 22 | case GDT_Int16: 23 | *data = new short[xSize * ySize * band]; 24 | break; 25 | case GDT_UInt32: 26 | *data = new unsigned long[xSize * ySize * band]; 27 | break; 28 | case GDT_Int32: 29 | *data = new long[xSize * ySize * band]; 30 | break; 31 | case GDT_Float32: 32 | *data = new float[xSize * ySize * band]; 33 | break; 34 | case GDT_Float64: 35 | *data = new double[xSize * ySize * band]; 36 | break; 37 | default: 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | unsigned char* ImagePress::imgSketch( 44 | float* buffer, 45 | GDALRasterBand* currentBand, 46 | int bandSize, 47 | double noValue 48 | ) 49 | { 50 | unsigned char* resBuffer = new unsigned char[bandSize]; 51 | double max, min; 52 | double minmax[2]; 53 | currentBand->ComputeRasterMinMax(1, minmax); 54 | min = minmax[0]; 55 | max = minmax[1]; 56 | if (min <= noValue && noValue <= max) 57 | min = 0; 58 | for (int i = 0; i < bandSize; ++i) 59 | { 60 | if (buffer[i] > max) 61 | resBuffer[i] = 255; 62 | else if (buffer[i] <= max && buffer[i] >= min) 63 | resBuffer[i] = static_cast( 64 | 255 - 255 * (max - buffer[i]) / (max - min)); 65 | else 66 | resBuffer[i] = 0; 67 | } 68 | return resBuffer; 69 | } 70 | 71 | QPixmap ImagePress::GDALRastertoPixmap(QList* imgBand) 72 | { 73 | rsize_t imgWidth = imgBand->at(0)->GetXSize(); 74 | rsize_t imgHeight = imgBand->at(0)->GetYSize(); 75 | GDALDataType dataType = imgBand->at(0)->GetRasterDataType(); 76 | // 首先分别读取RGB三个波段 77 | float* rBand = new float[imgWidth * imgHeight]; 78 | float* gBand = new float[imgWidth * imgHeight]; 79 | float* bBand = new float[imgWidth * imgHeight]; 80 | unsigned char* rBandUC, * gBandUC, * bBandUC; 81 | imgBand->at(0)->RasterIO( 82 | GF_Read, 0, 0, imgWidth, imgHeight, rBand, 83 | imgWidth, imgHeight, GDT_Float32, 0, 0 84 | ); 85 | imgBand->at(1)->RasterIO( 86 | GF_Read, 0, 0, imgWidth, imgHeight, gBand, 87 | imgWidth, imgHeight, GDT_Float32, 0, 0 88 | ); 89 | imgBand->at(2)->RasterIO( 90 | GF_Read, 0, 0, imgWidth, imgHeight, bBand, 91 | imgWidth, imgHeight, GDT_Float32, 0, 0 92 | ); 93 | // 分别拉伸每个波段并将Float转换为unsigned char 94 | rBandUC = imgSketch( 95 | rBand, imgBand->at(0), imgWidth * imgHeight, 96 | imgBand->at(0)->GetNoDataValue() 97 | ); 98 | gBandUC = imgSketch( 99 | gBand, imgBand->at(1), imgWidth * imgHeight, 100 | imgBand->at(1)->GetNoDataValue() 101 | ); 102 | bBandUC = imgSketch( 103 | bBand, imgBand->at(2), imgWidth * imgHeight, 104 | imgBand->at(2)->GetNoDataValue() 105 | ); 106 | // 将三个波段组合起来 107 | int bytePerLine = (imgWidth * 24 + 31) / 8; 108 | unsigned char* allBandUC = new unsigned char[bytePerLine * imgHeight * 3]; 109 | for (int h = 0; h < imgHeight; ++h) 110 | { 111 | for (int w = 0; w < imgWidth; ++w) 112 | { 113 | allBandUC[h * bytePerLine + w * 3 + 0] = rBandUC[h * imgWidth + w]; 114 | allBandUC[h * bytePerLine + w * 3 + 1] = gBandUC[h * imgWidth + w]; 115 | allBandUC[h * bytePerLine + w * 3 + 2] = bBandUC[h * imgWidth + w]; 116 | } 117 | } 118 | return QPixmap::fromImage( 119 | QImage(allBandUC, imgWidth, imgHeight, bytePerLine, QImage::Format_RGB888)); 120 | } 121 | 122 | bool ImagePress::saveTiffFromGDAL( 123 | std::string savePath, 124 | void* img, 125 | int nImgSizeX, 126 | int nImgSizeY, 127 | int nChannel, 128 | GDALDataType types, 129 | const char* projs, 130 | double* trans, 131 | int* pBandMaps 132 | ) 133 | { 134 | GDALDriver* pDriverMEM = GetGDALDriverManager()->GetDriverByName("GTiff"); 135 | if (!pDriverMEM) 136 | { 137 | GDALDestroyDriverManager(); 138 | return false; 139 | } 140 | GDALDataset* poDataset = pDriverMEM->Create( 141 | savePath.c_str(), nImgSizeX, nImgSizeY, nChannel, types, NULL); 142 | poDataset->SetProjection(projs); 143 | poDataset->SetGeoTransform(trans); 144 | if (!poDataset) 145 | { 146 | GDALClose(poDataset); 147 | GDALDestroyDriverManager(); 148 | return false; 149 | } 150 | poDataset->RasterIO( 151 | GF_Write, 0, 0, nImgSizeX, nImgSizeY, img, nImgSizeX, nImgSizeY, 152 | types, nChannel, pBandMaps, 0, 0, 0, NULL 153 | ); 154 | GDALClose(poDataset); 155 | return true; 156 | } 157 | 158 | bool ImagePress::saveTiffFromCVMask( 159 | std::string savePath, 160 | cv::Mat mask, 161 | const char* projs, 162 | double* trans 163 | ) 164 | { 165 | int nImgSizeX = mask.cols; 166 | int nImgSizeY = mask.rows; 167 | GDALDriver* pDriverMEM = GetGDALDriverManager()->GetDriverByName("GTiff"); 168 | if (!pDriverMEM) 169 | { 170 | GDALDestroyDriverManager(); 171 | return false; 172 | } 173 | GDALDataset* poDataset = pDriverMEM->Create( 174 | savePath.c_str(), nImgSizeX, nImgSizeY, 1, GDT_Byte, NULL); 175 | poDataset->SetProjection(projs); 176 | poDataset->SetGeoTransform(trans); 177 | if (!poDataset) 178 | { 179 | GDALClose(poDataset); 180 | GDALDestroyDriverManager(); 181 | return false; 182 | } 183 | poDataset->GetRasterBand(1)->RasterIO( 184 | GF_Write, 0, 0, nImgSizeX, nImgSizeY, mask.data, 185 | nImgSizeX, nImgSizeY, GDT_Byte, 0, 0, NULL 186 | ); 187 | GDALClose(poDataset); 188 | return true; 189 | } 190 | 191 | void ImagePress::calcWindowTrans(double trans[6], int locX, int locY) 192 | { 193 | trans[0] += locX * trans[1]; 194 | trans[3] += locY * trans[5]; 195 | } 196 | 197 | cv::Mat ImagePress::CVA(cv::Mat t1, cv::Mat t2) 198 | { 199 | float eps = 1e-12; 200 | std::vector t1Channels; 201 | cv::split(t1, t1Channels); 202 | std::vector t2Channels; 203 | cv::split(t2, t2Channels); 204 | int m = t1.rows; 205 | int n = t1.cols; 206 | cv::Mat intensity = cv::Mat::zeros(m, n, CV_64FC1); 207 | for (int i = 0; i < t1Channels.size(); ++i) 208 | { 209 | cv::Mat diff = t1Channels[i] - t2Channels[i]; 210 | cv::pow(diff, 2, diff); 211 | intensity += diff; 212 | } 213 | cv::pow(intensity, 0.5, intensity); 214 | // 变化强度 215 | double amin = 0, amax = 0; 216 | cv::Point minPt, maxPt; 217 | minMaxLoc(intensity, &amin, &amax, &minPt, &maxPt); 218 | intensity = 255.0f * (intensity - amin) / (amax - amin); 219 | // 伪彩色渲染 220 | intensity.convertTo(intensity, CV_8UC1); 221 | cv::applyColorMap(intensity, intensity, cv::COLORMAP_HOT); 222 | return intensity; 223 | } 224 | 225 | void ImagePress::saveResultFromPolygon( 226 | QString savePath, 227 | int labNum, 228 | int imgHeight, 229 | int imgWidth, 230 | QList polygons, 231 | std::string projs, 232 | double* trans 233 | ) 234 | { 235 | // 处理保存路径 236 | QStringList pathAndName = savePath.split("."); 237 | std::string iPath = pathAndName[0].toStdString(); 238 | std::string iExt = pathAndName[1].toStdString(); 239 | cv::String pseudoSavaPath = iPath + "_pseudo.png"; // 保存伪彩色 240 | std::string jsonSavePath = iPath + ".json"; // 保存json 241 | // 新建保存的图像 242 | cv::Mat pseudoResult = cv::Mat::zeros(cv::Size(imgWidth, imgHeight), CV_8UC3); 243 | cv::Mat result = cv::Mat::zeros(cv::Size(imgWidth, imgHeight), CV_8UC1); 244 | // 新建保存的json 245 | Json::StyledWriter writer; 246 | Json::Value polyList; 247 | std::ofstream os; 248 | os.open(jsonSavePath); 249 | // 标号小的覆盖标号大的 250 | for (int labIndex = labNum - 1; labIndex >= 0; labIndex--) 251 | { 252 | for (LabPolygon* poly : polygons) 253 | { 254 | if (poly->labelIndex == labIndex) 255 | { 256 | Json::Value polygons; 257 | polygons["labelIndex"] = poly->labelIndex; 258 | QColor c = poly->getColor(); 259 | cv::Scalar color = cv::Scalar(c.blue(), c.green(), c.red()); 260 | polygons["color"]["R"] = c.red(); 261 | polygons["color"]["G"] = c.green(); 262 | polygons["color"]["B"] = c.blue(); 263 | const int numPoint = poly->mPoints.count(); 264 | polygons["polygon"]["pointNumber"] = numPoint; 265 | cv::Point** cvPoints = new cv::Point * [1]; 266 | cvPoints[0] = new cv::Point[numPoint]; 267 | Json::Value points; 268 | for (int i = 0; i < numPoint; ++i) 269 | { 270 | cvPoints[0][i] = cv::Point( 271 | poly->mPoints.at(i)->x(), poly->mPoints.at(i)->y()); 272 | points.append(poly->mPoints.at(i)->x()); 273 | points.append(poly->mPoints.at(i)->y()); 274 | } 275 | const cv::Point* ppt[1] = { cvPoints[0] }; 276 | int npt[] = { numPoint }; 277 | cv::fillPoly(pseudoResult, ppt, npt, 1, color); 278 | cv::fillPoly(result, ppt, npt, 1, cv::Scalar(labIndex)); 279 | polygons["polygon"]["points"] = points; 280 | polyList.append(polygons); 281 | // 清除数组 282 | delete[] cvPoints[0]; 283 | delete[] cvPoints; 284 | } 285 | } 286 | } 287 | // 保存 288 | cv::imwrite(pseudoSavaPath, pseudoResult); 289 | if (iExt == "jpg" || iExt == "jpeg" || iExt == "png") 290 | { 291 | cv::String baseSavaPath = iPath + ".bmp"; 292 | cv::imwrite(baseSavaPath, result); 293 | } 294 | else 295 | { 296 | std::string baseSavaPath = iPath + ".tif"; 297 | GDALAllRegister(); 298 | CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); 299 | saveTiffFromCVMask(baseSavaPath, result, projs.c_str(), trans); 300 | GDALDestroyDriverManager(); 301 | } 302 | os << writer.write(polyList); 303 | os.close(); // 关闭json 304 | } 305 | 306 | std::vector ImagePress::calcUnique(cv::Mat mask) 307 | { 308 | auto begin = mask.begin(), end = mask.end(); 309 | auto last = std::unique(begin, end); 310 | std::sort(begin, last); 311 | last = std::unique(begin, last); 312 | return std::vector(begin, last); 313 | } 314 | 315 | void ImagePress::savePolygonFromMask(QString maskPath) 316 | { 317 | // 新建保存的json 318 | QStringList pathAndName = maskPath.split("."); 319 | std::string iPath = pathAndName[0].toStdString(); 320 | std::string jsonSavePath = iPath + ".json"; 321 | Json::StyledWriter writer; 322 | Json::Value polyList; 323 | std::ofstream os; 324 | os.open(jsonSavePath); 325 | // 读取图像并保存json 326 | cv::Mat mat = cv::imread(maskPath.toStdString()); 327 | cv::cvtColor(mat, mat, cv::COLOR_BGR2GRAY); 328 | cv::Mat uMat; 329 | mat.copyTo(uMat); 330 | std::vector unique = ImagePress::calcUnique(uMat); 331 | std::sort(unique.begin(), unique.end()); 332 | // 处理两类带255 333 | if (unique.size() == 2 && unique.at(1) == 255) 334 | { 335 | unique.at(1) = 1; 336 | cv::convertScaleAbs(mat, mat, 1.0 / 255, 0); 337 | } 338 | // 多类处理 339 | ColorMap cMap; 340 | QColor rgb; 341 | for (int i = 1; i < unique.size(); ++i) 342 | { 343 | int clas = static_cast(unique.at(i)); 344 | rgb = cMap.getColor(clas - 1); 345 | // 创建一个全为0的输出矩阵,与输入矩阵具有相同的大小和类型 346 | cv::Mat clasMat = cv::Mat::zeros(mat.size(), CV_8UC1); 347 | // 比较输入矩阵与所需值,将比较结果存储在输出矩阵中 348 | cv::compare(mat, clas, clasMat, cv::CMP_EQ); 349 | std::vector< std::vector > contours; 350 | std::vector hierarchy; 351 | cv::findContours( 352 | clasMat, 353 | contours, 354 | hierarchy, 355 | cv::RETR_TREE, 356 | cv::CHAIN_APPROX_TC89_KCOS 357 | ); 358 | std::vector contourPolys; 359 | double epsilon; 360 | for (size_t i = 0; i < hierarchy.size(); i++) 361 | { 362 | epsilon = 0.001 * cv::arcLength(cv::Mat(contours[i]), true); 363 | cv::approxPolyDP(cv::Mat(contours[i]), contourPolys, epsilon, true); 364 | Json::Value polygons; 365 | if (hierarchy[i][3] < 0) // 没有父轮廓 366 | { 367 | polygons["labelIndex"] = clas; 368 | polygons["color"]["R"] = rgb.red(); 369 | polygons["color"]["G"] = rgb.green(); 370 | polygons["color"]["B"] = rgb.blue(); 371 | } 372 | else // 孔洞 373 | { 374 | polygons["labelIndex"] = 0; 375 | polygons["color"]["R"] = 0; 376 | polygons["color"]["G"] = 0; 377 | polygons["color"]["B"] = 0; 378 | } 379 | polygons["polygon"]["pointNumber"] = contourPolys.size(); 380 | Json::Value points; 381 | for (cv::Point p : contourPolys) 382 | { 383 | points.append(p.x); 384 | points.append(p.y); 385 | } 386 | polygons["polygon"]["points"] = points; 387 | polyList.append(polygons); 388 | } 389 | } 390 | os << writer.write(polyList); 391 | os.close(); // 关闭json 392 | } 393 | 394 | bool ImagePress::openImage( 395 | QString imgPath, 396 | QPixmap& img, 397 | std::string& projs, 398 | double trans[6] 399 | ) 400 | { 401 | QFileInfo fileInfo(imgPath); 402 | QString ext = fileInfo.completeSuffix(); 403 | QList qtSupporExts = QImageReader::supportedImageFormats(); 404 | if (qtSupporExts.contains(ext.toLocal8Bit())) 405 | { 406 | img.load(imgPath); 407 | return true; 408 | } 409 | else // 使用gdal打开 410 | { 411 | GDALAllRegister(); // 注册所有的驱动 412 | CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); // 设置支持中文路径和文件名 413 | GDALDataset* poDataset = (GDALDataset*)GDALOpen( 414 | imgPath.toStdString().c_str(), GA_ReadOnly); 415 | if (poDataset == NULL) 416 | { 417 | GDALClose(poDataset); 418 | GDALDestroyDriverManager(); 419 | return false; 420 | } 421 | int bandCount = poDataset->GetRasterCount(); 422 | QList bandList; 423 | static std::vector HSI_RGB_LOAD; 424 | static int NOW_BAND_COUNT; 425 | if (bandCount < 3) // 小于3个波段只加载第一个波段 426 | { 427 | bandList.append(poDataset->GetRasterBand(1)); 428 | bandList.append(poDataset->GetRasterBand(1)); 429 | bandList.append(poDataset->GetRasterBand(1)); 430 | } 431 | else if (bandCount == 3) // RGB直接加载 432 | { 433 | bandList.append(poDataset->GetRasterBand(1)); 434 | bandList.append(poDataset->GetRasterBand(2)); 435 | bandList.append(poDataset->GetRasterBand(3)); 436 | } 437 | else // 多光谱计算oif再加载 438 | { 439 | // 只计算一次 440 | if (HSI_RGB_LOAD.empty() || bandCount != NOW_BAND_COUNT) 441 | HSI_RGB_LOAD = ImagePress::calcOIF(imgPath); 442 | bandList.append(poDataset->GetRasterBand(HSI_RGB_LOAD[0])); 443 | bandList.append(poDataset->GetRasterBand(HSI_RGB_LOAD[1])); 444 | bandList.append(poDataset->GetRasterBand(HSI_RGB_LOAD[2])); 445 | } 446 | NOW_BAND_COUNT = bandCount; 447 | projs = poDataset->GetProjectionRef(); 448 | poDataset->GetGeoTransform(trans); 449 | img = QPixmap(GDALRastertoPixmap(&bandList)); 450 | // 注销驱动 451 | GDALClose(poDataset); 452 | GDALDestroyDriverManager(); 453 | return true; 454 | } 455 | } 456 | 457 | bool ImagePress::splitTiff( 458 | QString imgPath, 459 | QString saveDir, 460 | int blockHeight, 461 | int blockWidth 462 | ) 463 | { 464 | // 获取文件名 465 | QString name = QFileInfo(imgPath).fileName().split(".")[0]; 466 | // 读取数据解析属性 467 | GDALAllRegister(); 468 | CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); 469 | GDALDataset* poDataset = (GDALDataset*)GDALOpen( 470 | imgPath.toStdString().c_str(), GA_ReadOnly); 471 | if (poDataset == NULL) 472 | { 473 | GDALClose(poDataset); 474 | GDALDestroyDriverManager(); 475 | return false; 476 | } 477 | int bandCount = poDataset->GetRasterCount(); 478 | rsize_t nYSize = poDataset->GetRasterYSize(); 479 | rsize_t nXSize = poDataset->GetRasterXSize(); 480 | GDALDataType types = poDataset->GetRasterBand(1)->GetRasterDataType(); 481 | double trans[6] = { 0 }; 482 | double windowTrans[6] = { 0 }; 483 | poDataset->GetGeoTransform(trans); 484 | const char* projs = poDataset->GetProjectionRef(); 485 | // 定义读取输入图像波段顺序 486 | int* pBandMaps = new int[bandCount]; 487 | for (int b = 0; b < bandCount; ++b) 488 | pBandMaps[b] = b + 1; 489 | // 循环分块并进行处理 490 | if (blockHeight > nXSize || blockWidth > nYSize) 491 | return false; 492 | void* pSrcData = nullptr; 493 | ImagePress::createArr(&pSrcData, types, blockWidth, blockHeight, bandCount); 494 | int row = 0; 495 | int tmpI, tmpJ; 496 | for (rsize_t i = 0; i < nYSize; i += blockWidth) 497 | { 498 | int col = 0; 499 | for (rsize_t j = 0; j < nXSize; j += blockHeight) 500 | { 501 | tmpI = i; 502 | tmpJ = j; 503 | if (i + blockHeight > nYSize) 504 | tmpI = nYSize - blockHeight; 505 | if (j + blockWidth > nXSize) 506 | tmpJ = nXSize - blockWidth; 507 | // 读取原始图像块 508 | poDataset->RasterIO( 509 | GF_Read, tmpJ, tmpI, blockWidth, blockHeight, pSrcData, 510 | blockWidth, blockHeight, types, bandCount, pBandMaps, 0, 0, 0, NULL 511 | ); 512 | // 保存 513 | std::string windowSavePath = (saveDir + QDir::separator() + name + \ 514 | "_" + QString::number(tmpJ) + \ 515 | "_" + QString::number(tmpI) + ".tif").toStdString(); 516 | std::copy(std::begin(trans), std::end(trans), std::begin(windowTrans)); 517 | calcWindowTrans(windowTrans, col * blockWidth, row * blockHeight); 518 | if (!saveTiffFromGDAL(windowSavePath, pSrcData, blockWidth, blockHeight, 519 | bandCount, types, projs, windowTrans, pBandMaps)) 520 | { 521 | delete[] pSrcData; 522 | GDALClose(poDataset); 523 | GDALDestroyDriverManager(); 524 | return false; 525 | } 526 | col++; 527 | } 528 | row++; 529 | } 530 | delete[] pSrcData; 531 | GDALClose(poDataset); 532 | GDALDestroyDriverManager(); 533 | return true; 534 | } 535 | 536 | bool ImagePress::mergeTiff(QString imgDir) 537 | { 538 | // 读取 539 | if (imgDir.isEmpty()) return false; 540 | QDir dir(imgDir); 541 | dir.setFilter(QDir::Files); 542 | dir.setSorting(QDir::Name); 543 | dir.setNameFilters(QString("*.tiff;*.tif").split(";")); 544 | QStringList subDirList = dir.entryList(); 545 | std::sort(subDirList.begin(), subDirList.end(), 546 | [](const QString& s1, const QString& s2) 547 | { 548 | QStringList fi1 = QFileInfo(s1).baseName().split("_"); 549 | QStringList fi2 = QFileInfo(s2).baseName().split("_"); 550 | int j1 = fi1.at(fi1.size() - 2).toInt(); 551 | int i1 = fi1.at(fi1.size() - 1).toInt(); 552 | int j2 = fi2.at(fi1.size() - 2).toInt(); 553 | int i2 = fi2.at(fi1.size() - 1).toInt(); 554 | if (j1 < j2) return true; 555 | else if (j1 > j2) return false; 556 | else 557 | { 558 | if (i1 < i2) return true; 559 | else return false; 560 | } 561 | } 562 | ); 563 | GDALAllRegister(); 564 | CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); 565 | // 读取第一个块,获取信息 566 | GDALDataset* tmpDataset = (GDALDataset*)GDALOpen( 567 | (imgDir + QDir::separator() + subDirList.at(0)).toStdString().c_str(), 568 | GA_ReadOnly 569 | ); 570 | if (tmpDataset == NULL) 571 | { 572 | GDALClose(tmpDataset); 573 | GDALDestroyDriverManager(); 574 | return false; 575 | } 576 | int bandCount = tmpDataset->GetRasterCount(); 577 | int* pBandMaps = new int[bandCount]; 578 | for (int b = 0; b < bandCount; ++b) pBandMaps[b] = b + 1; 579 | int blockX = tmpDataset->GetRasterXSize(); 580 | int blockY = tmpDataset->GetRasterYSize(); 581 | double trans[6] = { 0 }; 582 | tmpDataset->GetGeoTransform(trans); 583 | GDALDataType types = tmpDataset->GetRasterBand(1)->GetRasterDataType(); 584 | const char* projs = tmpDataset->GetProjectionRef(); 585 | GDALClose(tmpDataset); 586 | // 读取最后一个块的坐标信息 587 | QString tmp = subDirList.at(subDirList.size() - 1); 588 | QFileInfo tmpInfo = QFileInfo(tmp); 589 | QStringList ids = tmpInfo.baseName().split("_"); 590 | int jInd = ids.at(ids.size() - 2).toInt(); 591 | int iInd = ids.at(ids.size() - 1).toInt(); 592 | int nImgSizeX = jInd + blockX; 593 | int nImgSizeY = iInd + blockY; 594 | // 创建 595 | QString imgPath = imgDir + QDir::separator() + "merge.tif"; 596 | GDALDriver* pDriverMEM = GetGDALDriverManager()->GetDriverByName("GTiff"); 597 | if (!pDriverMEM) 598 | { 599 | GDALDestroyDriverManager(); 600 | return false; 601 | } 602 | GDALDataset* poDataset = pDriverMEM->Create( 603 | imgPath.toStdString().c_str(), nImgSizeX, nImgSizeY, bandCount, types, NULL); 604 | // 循环填入 605 | QFileInfo blockInfo; 606 | QStringList blockIds; 607 | void* tmpSrcData = nullptr; 608 | for (QString blockPath : subDirList) 609 | { 610 | blockInfo = QFileInfo(blockPath); 611 | blockIds = blockInfo.baseName().split("_"); 612 | GDALDataset* tmpDataset = (GDALDataset*)GDALOpen( 613 | (imgDir + QDir::separator() + blockPath).toStdString().c_str(), 614 | GA_ReadOnly 615 | ); 616 | jInd = blockIds.at(ids.size() - 2).toInt(); 617 | iInd = blockIds.at(ids.size() - 1).toInt(); 618 | if (tmpDataset == NULL) 619 | { 620 | GDALClose(tmpDataset); 621 | GDALDestroyDriverManager(); 622 | return false; 623 | } 624 | ImagePress::createArr(&tmpSrcData, types, blockX, blockY, bandCount); 625 | tmpDataset->RasterIO( 626 | GF_Read, 0, 0, blockX, blockY, tmpSrcData, blockX, blockY, 627 | types, bandCount, pBandMaps, 0, 0, 0, NULL 628 | ); 629 | poDataset->RasterIO( 630 | GF_Write, jInd, iInd, blockX, blockY, tmpSrcData, blockX, blockY, 631 | types, bandCount, pBandMaps, 0, 0, 0, NULL 632 | ); 633 | delete[] tmpSrcData; 634 | GDALClose(tmpDataset); 635 | poDataset->FlushCache(); 636 | } 637 | poDataset->SetProjection(projs); 638 | poDataset->SetGeoTransform(trans); 639 | GDALClose(poDataset); 640 | GDALDestroyDriverManager(); 641 | return true; 642 | } 643 | 644 | cv::Mat ImagePress::qpixmapToCVMat(QPixmap pimg) 645 | { 646 | QImage image = pimg.toImage(); 647 | cv::Mat mat; 648 | switch (image.format()) 649 | { 650 | case QImage::Format_ARGB32: 651 | case QImage::Format_RGB32: 652 | case QImage::Format_ARGB32_Premultiplied: 653 | mat = cv::Mat( 654 | image.height(), image.width(), CV_8UC4, 655 | (void*)image.constBits(), image.bytesPerLine() 656 | ); 657 | break; 658 | case QImage::Format_RGB888: 659 | mat = cv::Mat( 660 | image.height(), image.width(), CV_8UC3, 661 | (void*)image.constBits(), image.bytesPerLine() 662 | ); 663 | cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); 664 | break; 665 | case QImage::Format_Grayscale8: 666 | mat = cv::Mat( 667 | image.height(), image.width(), CV_8UC1, 668 | (void*)image.constBits(), image.bytesPerLine() 669 | ); 670 | break; 671 | } 672 | return mat; 673 | } 674 | 675 | std::vector ImagePress::calcOIF(QString hsiPath) 676 | { 677 | GDALDataset* ds = (GDALDataset*)GDALOpen( 678 | hsiPath.toStdString().c_str(), GA_ReadOnly); 679 | int bandCount = ds->GetRasterCount(); 680 | int width = ds->GetRasterXSize(); 681 | int height = ds->GetRasterYSize(); 682 | // 计算标准差和相关系数矩阵 683 | Eigen::MatrixXd coMatrix(bandCount, bandCount); 684 | std::vector stdDev; 685 | for (int i = 1; i <= bandCount; ++i) // 波段1 686 | { 687 | GDALRasterBand* band1 = ds->GetRasterBand(i); 688 | double* data1 = new double[width * height]; 689 | band1->RasterIO( 690 | GF_Read, 0, 0, width, height, data1, width, height, GDT_Float64, 0, 0); 691 | Eigen::Map mat1(data1, height, width); 692 | // 计算标准差 693 | double std = mat1.array().sqrt().matrix().mean(); 694 | stdDev.push_back(std); 695 | for (int j = i + 1; j <= bandCount; ++j) // 波段2 696 | { 697 | GDALRasterBand* band2 = ds->GetRasterBand(j); 698 | double* data2 = new double[width * height]; 699 | band2->RasterIO( 700 | GF_Read, 0, 0, width, height, data2, 701 | width, height, GDT_Float64, 0, 0 702 | ); 703 | Eigen::Map mat2(data2, height, width); 704 | // 计算相关系数矩阵 705 | double corr = (mat1.array() - \ 706 | mat1.mean()).matrix().normalized().cwiseProduct((mat2.array() - \ 707 | mat2.mean()).matrix().normalized()).sum() / ((double)width * height); 708 | coMatrix(i - 1, j - 1) = corr; 709 | coMatrix(j - 1, i - 1) = corr; 710 | // 释放内存 711 | delete[] data2; 712 | } 713 | delete[] data1; 714 | } 715 | // 计算OIF值 716 | double maxOIF(0), oif(0); 717 | std::vector best_bands = { 0, 0, 0 }; 718 | for (int i = 1; i <= bandCount; ++i) 719 | { 720 | for (int j = i + 1; j <= bandCount; ++j) 721 | { 722 | for (int k = j + 1; k <= bandCount; ++k) 723 | { 724 | oif = (stdDev[i - 1] + stdDev[j - 1] + stdDev[k - 1]) / (\ 725 | coMatrix(i - 1, j - 1) + \ 726 | coMatrix(i - 1, k - 1) + \ 727 | coMatrix(j - 1, k - 1)); 728 | if (oif > maxOIF) { 729 | maxOIF = oif; 730 | best_bands[0] = i; 731 | best_bands[1] = j; 732 | best_bands[2] = k; 733 | } 734 | } 735 | } 736 | } 737 | GDALClose(ds); 738 | return best_bands; 739 | } 740 | 741 | bool ImagePress::maskIsEmpty(QString maskPath) 742 | { 743 | bool isEmpty = false; 744 | GDALAllRegister(); 745 | CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); 746 | CPLSetConfigOption("GDAL_PAM_ENABLED", "NO"); // 不创建aux.xml文件 747 | GDALDataset* maskData = (GDALDataset*)GDALOpen( 748 | maskPath.toStdString().c_str(), GA_ReadOnly); 749 | double mMin, mMax, mMean, mStddev; 750 | maskData->GetRasterBand(1)->ComputeStatistics( 751 | true, &mMin, &mMax, &mMean, &mStddev, NULL, NULL); 752 | if (mMin == mMax && mMin == 0) isEmpty = true; 753 | GDALClose(maskData); 754 | GDALDestroyDriverManager(); 755 | return isEmpty; 756 | } 757 | -------------------------------------------------------------------------------- /labcd/utils/imgpress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../widgets/labpolygon.h" 9 | 10 | class ImagePress 11 | { 12 | private: 13 | static bool createArr( 14 | void** data, GDALDataType types, int xSize, int ySize, int band); 15 | static unsigned char* imgSketch( 16 | float* buffer, 17 | GDALRasterBand* currentBand, 18 | int bandSize, 19 | double noValue 20 | ); 21 | static QPixmap GDALRastertoPixmap(QList* imgBand); 22 | static bool saveTiffFromGDAL( 23 | std::string savePath, 24 | void* img, 25 | int nImgSizeX, 26 | int nImgSizeY, 27 | int nChannel, 28 | GDALDataType types, 29 | const char* projs, 30 | double* trans, 31 | int* pBandMaps 32 | ); 33 | static bool saveTiffFromCVMask( 34 | std::string savePath, 35 | cv::Mat mask, 36 | const char* projs, 37 | double* trans 38 | ); 39 | static void calcWindowTrans(double trans[6], int locX, int locY); 40 | static std::vector calcUnique(cv::Mat mask); 41 | 42 | public: 43 | static cv::Mat CVA(cv::Mat t1, cv::Mat t2); 44 | static void saveResultFromPolygon( 45 | QString savePath, 46 | int labNum, 47 | int imgHeight, 48 | int imgWidth, 49 | QList polygons, 50 | std::string projs = "", 51 | double* trans = NULL 52 | ); 53 | static void savePolygonFromMask(QString maskPath); 54 | static bool openImage( 55 | QString imgPath, 56 | QPixmap& img, 57 | std::string& projs, 58 | double trans[6] 59 | ); 60 | static bool splitTiff( 61 | QString imgPath, 62 | QString saveDir, 63 | int blockHeight = 512, 64 | int blockWidth = 512 65 | ); 66 | static bool mergeTiff(QString imgDir); 67 | static cv::Mat qpixmapToCVMat(QPixmap pimg); 68 | static std::vector calcOIF(QString hsiPath); 69 | static bool maskIsEmpty(QString maskPath); 70 | }; 71 | -------------------------------------------------------------------------------- /labcd/utils/label.cpp: -------------------------------------------------------------------------------- 1 | #include "label.h" 2 | 3 | Label::Label() 4 | { 5 | Label::init(); 6 | } 7 | 8 | Label::Label(int index, QString name, QColor color) 9 | { 10 | Label::reSet(index, name, color); 11 | } 12 | 13 | Label::~Label() 14 | { 15 | 16 | } 17 | 18 | void Label::init() 19 | { 20 | this->index = -1; 21 | this->name = ""; 22 | this->color = QColor(); 23 | } 24 | 25 | void Label::reSet(int index, QString name, QColor color) 26 | { 27 | this->index = index; 28 | this->name = name; 29 | this->color = color; 30 | } 31 | 32 | void Label::setIndex(int index) 33 | { 34 | this->index = index; 35 | } 36 | 37 | int Label::getIndex() 38 | { 39 | return this->index; 40 | } 41 | 42 | void Label::setName(QString name) 43 | { 44 | this->name = name; 45 | } 46 | 47 | QString Label::getName() 48 | { 49 | return this->name; 50 | } 51 | 52 | void Label::setColor(QColor color) 53 | { 54 | this->color = color; 55 | } 56 | 57 | QColor Label::getColor() 58 | { 59 | return this->color; 60 | } 61 | -------------------------------------------------------------------------------- /labcd/utils/label.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Label 8 | { 9 | private: 10 | int index; 11 | QString name; 12 | QColor color; 13 | 14 | public: 15 | Label(); 16 | Label(int index, QString name, QColor color); 17 | ~Label(); 18 | void init(); 19 | void reSet(int index, QString name, QColor color); 20 | void setIndex(int index); 21 | int getIndex(); 22 | void setName(QString name); 23 | QString getName(); 24 | void setColor(QColor color); 25 | QColor getColor(); 26 | }; 27 | -------------------------------------------------------------------------------- /labcd/widgets/annotationscence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "annotationscence.h" 4 | 5 | AnnotationScence::AnnotationScence() 6 | : QGraphicsScene() 7 | { 8 | scaleRate = 1.0; 9 | // 十字丝 10 | crossPen = new QPen(); 11 | crossPen->setWidth(2); 12 | crossPen->setColor(QColor(0, 0, 0, 127)); 13 | coords = nullptr; 14 | } 15 | 16 | AnnotationScence::~AnnotationScence() 17 | { 18 | 19 | } 20 | 21 | bool AnnotationScence::getItemHovering() 22 | { 23 | for (LabPolygon* poly : polygonItems) 24 | { 25 | if (poly->itemHovering) 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | bool AnnotationScence::getPolyHovering() 32 | { 33 | for (LabPolygon* poly : polygonItems) 34 | { 35 | if (poly->polyHovering) 36 | return true; 37 | } 38 | return false; 39 | } 40 | 41 | bool AnnotationScence::getLineHovering() 42 | { 43 | for (LabPolygon* poly : polygonItems) 44 | { 45 | if (poly->lineHovering) 46 | return true; 47 | } 48 | return false; 49 | } 50 | 51 | void AnnotationScence::updateCrossPenSize() 52 | { 53 | crossPen->setWidth(std::max(1, int(2 / scaleRate + 1e-12))); 54 | } 55 | 56 | void AnnotationScence::resetScence() 57 | { 58 | nowItem = nullptr; 59 | drawing = false; 60 | clearFocusAndSelected(); 61 | } 62 | 63 | void AnnotationScence::finished() 64 | { 65 | if (nowItem != nullptr) 66 | { 67 | if (drawing == true && nowItem->getLen() < 3) 68 | nowItem->remove(); 69 | } 70 | resetScence(); // 清理和置零 71 | } 72 | 73 | void AnnotationScence::setScaleRate(double zoomAll) 74 | { 75 | scaleRate = zoomAll; 76 | } 77 | 78 | void AnnotationScence::updatePolygonSize() 79 | { 80 | for (LabPolygon* poly : polygonItems) 81 | { 82 | for (LabGrid* grip : poly->mItems) 83 | grip->updateSize(); 84 | for (LabLine* line : poly->mLines) 85 | line->updateWidth(); 86 | } 87 | } 88 | 89 | void AnnotationScence::setColor(QColor _insideColor, QColor _borderColor) 90 | { 91 | insideColor = _insideColor; 92 | borderColor = _borderColor; 93 | } 94 | 95 | QColor AnnotationScence::getCrossPenColor() 96 | { 97 | return crossPen->color(); 98 | } 99 | 100 | void AnnotationScence::setCrossPenColor(QColor color) 101 | { 102 | crossPen->setColor(color); 103 | } 104 | 105 | bool AnnotationScence::hovering() 106 | { 107 | if (getItemHovering() || getPolyHovering() || getLineHovering()) 108 | { 109 | return true; 110 | } 111 | return false; 112 | } 113 | 114 | int AnnotationScence::findFocusPolygon() 115 | { 116 | int focusIndex = -1; 117 | for (int i = 0; i < polygonItems.count(); ++i) 118 | { 119 | if (polygonItems.at(i)->hasFocus()) 120 | { 121 | focusIndex = i; 122 | break; 123 | } 124 | } 125 | return focusIndex; 126 | } 127 | 128 | void AnnotationScence::delPoly(int index) 129 | { 130 | if (index != -1) 131 | polygonItems[index]->remove(); 132 | } 133 | 134 | void AnnotationScence::removeAllPolygons() 135 | { 136 | int numPoly = polygonItems.count(); 137 | for (int i = numPoly - 1; i >= 0; --i) 138 | delPoly(i); 139 | } 140 | 141 | void AnnotationScence::clearAllFocus() 142 | { 143 | for (QGraphicsItem* obj : items()) 144 | { 145 | if (obj->hasFocus()) 146 | obj->clearFocus(); 147 | } 148 | } 149 | 150 | void AnnotationScence::clearFocusAndSelected() 151 | { 152 | clearAllFocus(); 153 | clearSelection(); 154 | } 155 | 156 | void AnnotationScence::PressedAddPoint(QPointF point) 157 | { 158 | if (imgWidth != 0) 159 | { 160 | if (nowItem == nullptr) 161 | { 162 | nowItem = new LabPolygon( 163 | this, polygonItems.count(), labelIndex, 164 | imgWidth, imgHeight, insideColor, borderColor, opacity 165 | ); 166 | addItem(nowItem); 167 | polygonItems.push_back(nowItem); 168 | } 169 | nowItem->addPointLast(point); 170 | // 选择 171 | clearFocusAndSelected(); 172 | nowItem->mItems[nowItem->mItems.count() - 1]->setSelected(true); 173 | } 174 | } 175 | 176 | void AnnotationScence::mousePressEvent(QGraphicsSceneMouseEvent* ev) 177 | { 178 | // 右键或点击多边形激活则完成 179 | if (ev->button() == Qt::RightButton || (hovering() && drawing)) 180 | { 181 | finished(); 182 | emit mouseOptRequest(-1, -1, OptTypes::SceneMousePress, ev); 183 | } 184 | // 其他操作 185 | else 186 | { 187 | QPointF p = ev->scenePos(); 188 | if (p.x() < 0 || p.x() > imgWidth || p.y() < 0 || p.y() > imgHeight) 189 | return; // 超出范围 190 | if (!hovering()) // 非激活状态 191 | { 192 | // 当选择有标签的时候,左键添加 193 | if (ev->button() == Qt::LeftButton && labelIndex != -1) 194 | { 195 | PressedAddPoint(p); 196 | drawing = true; 197 | emit mouseOptRequest(-1, -1, OptTypes::SceneMousePress, ev); 198 | } 199 | } 200 | else 201 | { 202 | for (LabPolygon* poly : polygonItems) 203 | { 204 | if (poly->polyHovering) 205 | emit focusRequest(poly->labelIndex); 206 | } 207 | } 208 | QGraphicsScene::mousePressEvent(ev); 209 | } 210 | } 211 | 212 | void AnnotationScence::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev) 213 | { 214 | QGraphicsItem* nowObj = itemAt(ev->scenePos(), QTransform()); 215 | if (nowObj == nullptr) 216 | return; 217 | switch ((int)nowObj->zValue()) 218 | { 219 | case 10: // 使双击对多边形无效 220 | break; 221 | default: 222 | QGraphicsScene::mouseDoubleClickEvent(ev); 223 | break; 224 | } 225 | } 226 | 227 | void AnnotationScence::getLabel(Label* label) 228 | { 229 | // 完成之前的 230 | finished(); 231 | // 新设定 232 | labelIndex = label->getIndex(); 233 | insideColor = label->getColor(); 234 | borderColor = label->getColor(); 235 | } 236 | 237 | void AnnotationScence::getImageSize(int Width, int Height) 238 | { 239 | imgWidth = Width; 240 | imgHeight = Height; 241 | } 242 | 243 | void AnnotationScence::copyMouseOpt( 244 | int polyIndex, int subIndex, OptTypes optType, QEvent* ev) 245 | { 246 | blockSignals(true); 247 | switch (optType) 248 | { 249 | case OptTypes::SceneMousePress: 250 | mousePressEvent((QGraphicsSceneMouseEvent*)ev); 251 | break; 252 | case OptTypes::SceneMouseDoubleClick: 253 | mouseDoubleClickEvent((QGraphicsSceneMouseEvent*)ev); 254 | break; 255 | case OptTypes::PolyHoverEnter: 256 | if (nowItem != nullptr) 257 | nowItem->hoverEnterEvent((QGraphicsSceneHoverEvent*)ev); 258 | else 259 | { 260 | if (polygonItems.count() != 0) 261 | polygonItems[polyIndex % polygonItems.count()]->hoverEnterEvent( 262 | (QGraphicsSceneHoverEvent*)ev); 263 | } 264 | break; 265 | case OptTypes::PolyHoverLeave: 266 | if (nowItem != nullptr) 267 | nowItem->hoverLeaveEvent((QGraphicsSceneHoverEvent*)ev); 268 | else 269 | { 270 | if (polygonItems.count() != 0) 271 | polygonItems[polyIndex % polygonItems.count()]->hoverLeaveEvent( 272 | (QGraphicsSceneHoverEvent*)ev); 273 | } 274 | break; 275 | case OptTypes::PolyMousePress: 276 | if (nowItem != nullptr) 277 | nowItem->mousePressEvent((QGraphicsSceneMouseEvent*)ev); 278 | else 279 | { 280 | if (polygonItems.count() != 0) 281 | polygonItems[polyIndex % polygonItems.count()]->mousePressEvent( 282 | (QGraphicsSceneMouseEvent*)ev); 283 | } 284 | break; 285 | case OptTypes::PolyMouseRelease: 286 | if (nowItem != nullptr) 287 | nowItem->mouseReleaseEvent((QGraphicsSceneMouseEvent*)ev); 288 | else 289 | { 290 | if (polygonItems.count() != 0) 291 | polygonItems[polyIndex % polygonItems.count()]->mouseReleaseEvent( 292 | (QGraphicsSceneMouseEvent*)ev); 293 | } 294 | break; 295 | case OptTypes::PolyFocusIn: 296 | if (nowItem != nullptr) 297 | nowItem->focusInEvent((QFocusEvent*)ev); 298 | else 299 | { 300 | if (polygonItems.count() != 0) 301 | polygonItems[polyIndex % polygonItems.count()]->focusInEvent( 302 | (QFocusEvent*)ev); 303 | } 304 | break; 305 | case OptTypes::PolyFocusOut: 306 | if (nowItem != nullptr) 307 | nowItem->focusOutEvent((QFocusEvent*)ev); 308 | else 309 | { 310 | if (polygonItems.count() != 0) 311 | polygonItems[polyIndex % polygonItems.count()]->focusOutEvent( 312 | (QFocusEvent*)ev); 313 | } 314 | break; 315 | case OptTypes::LineHoverEnter: 316 | if (nowItem != nullptr) 317 | { 318 | int lens = nowItem->mLines.count(); 319 | nowItem->mLines[subIndex % lens]->hoverEnterEvent( 320 | (QGraphicsSceneHoverEvent*)ev); 321 | } 322 | else 323 | { 324 | if (polygonItems.count() != 0) 325 | { 326 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 327 | mLines.count(); 328 | polygonItems[polyIndex % polygonItems.count()]-> \ 329 | mLines[subIndex % lens]-> \ 330 | hoverEnterEvent((QGraphicsSceneHoverEvent*)ev); 331 | } 332 | } 333 | break; 334 | case OptTypes::LineHoverLeave: 335 | if (nowItem != nullptr) 336 | { 337 | int lens = nowItem->mLines.count(); 338 | nowItem->mLines[subIndex % lens]->hoverLeaveEvent( 339 | (QGraphicsSceneHoverEvent*)ev); 340 | } 341 | else 342 | { 343 | if (polygonItems.count() != 0) 344 | { 345 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 346 | mLines.count(); 347 | polygonItems[polyIndex % polygonItems.count()]-> \ 348 | mLines[subIndex % lens]-> \ 349 | hoverLeaveEvent((QGraphicsSceneHoverEvent*)ev); 350 | } 351 | } 352 | break; 353 | case OptTypes::LineMousePress: 354 | if (nowItem != nullptr) 355 | { 356 | int lens = nowItem->mLines.count(); 357 | nowItem->mLines[subIndex % lens]->mousePressEvent( 358 | (QGraphicsSceneMouseEvent*)ev); 359 | } 360 | else 361 | { 362 | if (polygonItems.count() != 0) 363 | { 364 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 365 | mLines.count(); 366 | polygonItems[polyIndex % polygonItems.count()]-> \ 367 | mLines[subIndex % lens]-> \ 368 | mousePressEvent((QGraphicsSceneMouseEvent*)ev); 369 | } 370 | } 371 | break; 372 | case OptTypes::LineMouseRelease: 373 | if (nowItem != nullptr) 374 | { 375 | int lens = nowItem->mLines.count(); 376 | nowItem->mLines[subIndex % lens]->mouseReleaseEvent( 377 | (QGraphicsSceneMouseEvent*)ev); 378 | } 379 | else 380 | { 381 | if (polygonItems.count() != 0) 382 | { 383 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 384 | mLines.count(); 385 | polygonItems[polyIndex % polygonItems.count()]-> \ 386 | mLines[subIndex % lens]-> \ 387 | mouseReleaseEvent((QGraphicsSceneMouseEvent*)ev); 388 | } 389 | } 390 | break; 391 | case OptTypes::LineMouseDoubleClick: 392 | if (nowItem != nullptr) 393 | { 394 | int lens = nowItem->mLines.count(); 395 | nowItem->mLines[subIndex % lens]->mouseDoubleClickEvent( 396 | (QGraphicsSceneMouseEvent*)ev); 397 | } 398 | else 399 | { 400 | if (polygonItems.count() != 0) 401 | { 402 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 403 | mLines.count(); 404 | polygonItems[polyIndex % polygonItems.count()]-> \ 405 | mLines[subIndex % lens]-> \ 406 | mouseDoubleClickEvent((QGraphicsSceneMouseEvent*)ev); 407 | } 408 | } 409 | break; 410 | case OptTypes::GridHoverEnter: 411 | if (nowItem != nullptr) 412 | { 413 | int lens = nowItem->mItems.count(); 414 | nowItem->mItems[subIndex % lens]->hoverEnterEvent( 415 | (QGraphicsSceneHoverEvent*)ev); 416 | } 417 | else 418 | { 419 | if (polygonItems.count() != 0) 420 | { 421 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 422 | mItems.count(); 423 | polygonItems[polyIndex % polygonItems.count()]-> \ 424 | mItems[subIndex % lens]-> \ 425 | hoverEnterEvent((QGraphicsSceneHoverEvent*)ev); 426 | } 427 | } 428 | break; 429 | case OptTypes::GridHoverLeave: 430 | if (nowItem != nullptr) 431 | { 432 | int lens = nowItem->mItems.count(); 433 | nowItem->mItems[subIndex % lens]->hoverLeaveEvent( 434 | (QGraphicsSceneHoverEvent*)ev); 435 | } 436 | else 437 | { 438 | if (polygonItems.count() != 0) 439 | { 440 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 441 | mItems.count(); 442 | polygonItems[polyIndex % polygonItems.count()]-> \ 443 | mItems[subIndex % lens]-> \ 444 | hoverLeaveEvent((QGraphicsSceneHoverEvent*)ev); 445 | } 446 | } 447 | break; 448 | case OptTypes::GridMousePress: 449 | if (nowItem != nullptr) 450 | { 451 | int lens = nowItem->mItems.count(); 452 | nowItem->mItems[subIndex % lens]->mousePressEvent( 453 | (QGraphicsSceneMouseEvent*)ev); 454 | } 455 | else 456 | { 457 | if (polygonItems.count() != 0) 458 | { 459 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 460 | mItems.count(); 461 | polygonItems[polyIndex % polygonItems.count()]-> \ 462 | mItems[subIndex % lens]-> \ 463 | mousePressEvent((QGraphicsSceneMouseEvent*)ev); 464 | } 465 | } 466 | break; 467 | case OptTypes::GridMouseRelease: 468 | if (nowItem != nullptr) 469 | { 470 | int lens = nowItem->mItems.count(); 471 | nowItem->mItems[subIndex % lens]->mouseReleaseEvent( 472 | (QGraphicsSceneMouseEvent*)ev); 473 | } 474 | else 475 | { 476 | if (polygonItems.count() != 0) 477 | { 478 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 479 | mItems.count(); 480 | polygonItems[polyIndex % polygonItems.count()]-> \ 481 | mItems[subIndex % lens]-> \ 482 | mouseReleaseEvent((QGraphicsSceneMouseEvent*)ev); 483 | } 484 | } 485 | break; 486 | case OptTypes::GridMouseMove: 487 | if (nowItem != nullptr) 488 | { 489 | int lens = nowItem->mItems.count(); 490 | nowItem->mItems[subIndex % lens]->mouseMoveEvent( 491 | (QGraphicsSceneMouseEvent*)ev); 492 | } 493 | else 494 | { 495 | if (polygonItems.count() != 0) 496 | { 497 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 498 | mItems.count(); 499 | polygonItems[polyIndex % polygonItems.count()]-> \ 500 | mItems[subIndex % lens]-> \ 501 | mouseMoveEvent((QGraphicsSceneMouseEvent*)ev); 502 | } 503 | } 504 | break; 505 | case OptTypes::GridMouseDoubleClick: 506 | if (nowItem != nullptr) 507 | { 508 | int lens = nowItem->mItems.count(); 509 | nowItem->mItems[subIndex % lens]->mouseDoubleClickEvent( 510 | (QGraphicsSceneMouseEvent*)ev); 511 | } 512 | else 513 | { 514 | if (polygonItems.count() != 0) 515 | { 516 | int lens = polygonItems[polyIndex % polygonItems.count()]-> \ 517 | mItems.count(); 518 | polygonItems[polyIndex % polygonItems.count()]-> \ 519 | mItems[subIndex % lens]-> \ 520 | mouseDoubleClickEvent((QGraphicsSceneMouseEvent*)ev); 521 | } 522 | } 523 | break; 524 | default: 525 | break; 526 | } 527 | blockSignals(false); 528 | } 529 | 530 | void AnnotationScence::drawForeground(QPainter* painter, const QRectF& rect) 531 | { 532 | if (coords != nullptr && *coords != QPointF(-1, -1)) 533 | { 534 | painter->setClipRect(rect); 535 | painter->setPen(*crossPen); 536 | painter->drawLine( 537 | int(coords->x()), 538 | int(rect.top()), int(coords->x()), int(rect.bottom() + 1) 539 | ); 540 | painter->drawLine( 541 | int(rect.left()), 542 | int(coords->y()), 543 | int(rect.right() + 1), int(coords->y()) 544 | ); 545 | } 546 | } 547 | 548 | void AnnotationScence::onMouseChanged(double x, double y) 549 | { 550 | coords = new QPointF(x, y); 551 | invalidate(); 552 | } 553 | -------------------------------------------------------------------------------- /labcd/widgets/annotationscence.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "labpolygon.h" 8 | #include "opttypes.h" 9 | #include "../utils/label.h" 10 | 11 | class AnnotationScence : public QGraphicsScene 12 | { 13 | Q_OBJECT 14 | 15 | private: 16 | double scaleRate; 17 | QColor insideColor; 18 | QColor borderColor; 19 | QPen* crossPen; 20 | QPointF* coords; 21 | 22 | bool getItemHovering(); 23 | bool getPolyHovering(); 24 | bool getLineHovering(); 25 | void updateCrossPenSize(); 26 | 27 | public: 28 | int labelIndex = -1; 29 | int imgWidth = 0; 30 | int imgHeight = 0; 31 | double opacity = 0.5; 32 | bool drawing = false; 33 | QList polygonItems; 34 | LabPolygon* nowItem = nullptr; 35 | 36 | AnnotationScence(); 37 | ~AnnotationScence(); 38 | void resetScence(); 39 | void finished(); 40 | void setScaleRate(double zoomAll); 41 | void updatePolygonSize(); 42 | void setColor(QColor _insideColor, QColor _borderColor); 43 | QColor getCrossPenColor(); 44 | void setCrossPenColor(QColor color); 45 | bool hovering(); 46 | int findFocusPolygon(); 47 | void delPoly(int preFocusIndex); 48 | void removeAllPolygons(); 49 | void clearAllFocus(); 50 | void clearFocusAndSelected(); 51 | // 同步方法 52 | void PressedAddPoint(QPointF point); 53 | // 鼠标事件 54 | void mousePressEvent(QGraphicsSceneMouseEvent* ev); 55 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev); 56 | void drawForeground(QPainter* painter, const QRectF& rect); 57 | void onMouseChanged(double x, double y); 58 | 59 | signals: 60 | void focusRequest(int labelIndex); 61 | void mouseOptRequest(int polyIndex, int subIndex, OptTypes optType, QEvent* ev); 62 | 63 | public slots: 64 | void getLabel(Label* label); 65 | void getImageSize(int Width, int Height); 66 | void copyMouseOpt(int polyIndex, int subIndex, OptTypes optType, QEvent* ev); 67 | }; 68 | -------------------------------------------------------------------------------- /labcd/widgets/annotationview.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "annotationview.h" 4 | 5 | AnnotationView::AnnotationView( 6 | AnnotationScence* scence, QWidget* parent) 7 | : QGraphicsView(scence, parent) 8 | { 9 | setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); 10 | setMouseTracking(true); 11 | setTransformationAnchor(QGraphicsView::NoAnchor); 12 | setResizeAnchor(QGraphicsView::NoAnchor); 13 | } 14 | 15 | AnnotationView::~AnnotationView() 16 | { 17 | 18 | } 19 | 20 | double AnnotationView::limitZoom(double min, double now, double max) 21 | { 22 | if (now > max) 23 | return max; 24 | else if (now < min) 25 | return min; 26 | else 27 | return now; 28 | } 29 | 30 | void AnnotationView::sendSyncSignal() 31 | { 32 | emit syncRequest( 33 | horizontalScrollBar()->value(), 34 | verticalScrollBar()->value(), 35 | transform(), 36 | zoomAll 37 | ); 38 | } 39 | 40 | bool AnnotationView::checkZoomAll() 41 | { 42 | if (zoomAll < minRange || zoomAll > maxRange) 43 | return false; 44 | else 45 | return true; 46 | } 47 | 48 | void AnnotationView::setZoomAll(double value) 49 | { 50 | zoomAll = value; 51 | } 52 | 53 | double AnnotationView::getZoomAll() 54 | { 55 | return zoomAll; 56 | } 57 | 58 | void AnnotationView::syncTranslate(int hPos, int vPos, QTransform tf, double zoom) 59 | { 60 | zoomAll = zoom; 61 | setTransform(tf); 62 | horizontalScrollBar()->setValue(hPos); 63 | verticalScrollBar()->setValue(vPos); 64 | } 65 | 66 | void AnnotationView::scaleZoom(double _zoom) 67 | { 68 | zoomAll *= _zoom; 69 | // 限制缩放 70 | if (AnnotationView::checkZoomAll()) 71 | scale(_zoom, _zoom); 72 | else 73 | zoomAll = AnnotationView::limitZoom(minRange, zoomAll, maxRange); 74 | emit zoomRequest(zoomAll); 75 | } 76 | 77 | void AnnotationView::wheelEvent(QWheelEvent* ev) 78 | { 79 | if (ev->modifiers() && Qt::ControlModifier) 80 | { 81 | double zoom = 1.0 + ev->angleDelta().y() / 2880.0; // 倍率 82 | zoomAll *= zoom; 83 | // 限制缩放 84 | if (AnnotationView::checkZoomAll()) 85 | { 86 | QPointF oldPos = mapToScene(ev->position().toPoint()); 87 | scale(zoom, zoom); 88 | QPointF newPos = mapToScene(ev->position().toPoint()); 89 | QPointF delta = newPos - oldPos; 90 | translate(delta.x(), delta.y()); 91 | } 92 | else 93 | zoomAll = AnnotationView::limitZoom(minRange, zoomAll, maxRange); 94 | emit zoomRequest(zoomAll); 95 | ev->ignore(); // 忽略滚动条 96 | } 97 | else 98 | QGraphicsView::wheelEvent(ev); 99 | } 100 | 101 | void AnnotationView::mouseMoveEvent(QMouseEvent* ev) 102 | { 103 | QPointF mousePos = QPointF(mapToScene(ev->pos())); 104 | emit mousePosChanged(mousePos.x(), mousePos.y()); 105 | // 出现滚动条才能滚动 106 | if (middleClicking && \ 107 | (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())) 108 | { 109 | endPos = new QPoint(ev->pos() / zoomAll - *startPos / zoomAll); 110 | point = new QPoint(*point + *endPos); 111 | startPos = new QPoint(ev->pos()); 112 | translate(endPos->x(), endPos->y()); 113 | } 114 | QGraphicsView::mouseMoveEvent(ev); 115 | } 116 | 117 | void AnnotationView::mousePressEvent(QMouseEvent* ev) 118 | { 119 | if (ev->button() == Qt::MiddleButton) 120 | { 121 | middleClicking = true; 122 | startPos = new QPoint(ev->pos()); 123 | } 124 | QGraphicsView::mousePressEvent(ev); 125 | } 126 | 127 | void AnnotationView::mouseReleaseEvent(QMouseEvent* ev) 128 | { 129 | middleClicking = false; 130 | QGraphicsView::mouseReleaseEvent(ev); 131 | } 132 | 133 | void AnnotationView::scale(qreal sx, qreal sy) 134 | { 135 | QGraphicsView::scale(sx, sy); 136 | sendSyncSignal(); 137 | } 138 | 139 | void AnnotationView::translate(qreal dx, qreal dy) 140 | { 141 | QGraphicsView::translate(dx, dy); 142 | sendSyncSignal(); 143 | } 144 | -------------------------------------------------------------------------------- /labcd/widgets/annotationview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "annotationscence.h" 8 | 9 | class AnnotationView : public QGraphicsView 10 | { 11 | Q_OBJECT 12 | 13 | private: 14 | QPoint* point = new QPoint(0, 0); 15 | QPoint* startPos = new QPoint(0, 0); 16 | QPoint* endPos = new QPoint(0, 0); 17 | bool middleClicking = false; // 中间用于移动 18 | const double minRange = 0.02; 19 | const double maxRange = 50; 20 | double zoomAll = 1; 21 | 22 | double limitZoom(double min, double now, double max); 23 | void sendSyncSignal(); 24 | 25 | public: 26 | AnnotationView( 27 | AnnotationScence* scence, 28 | QWidget* parent = nullptr 29 | ); 30 | ~AnnotationView(); 31 | bool checkZoomAll(); 32 | void setZoomAll(double value); 33 | double getZoomAll(); 34 | void syncTranslate(int hPos, int vPos, QTransform tf, double zoom); 35 | void scaleZoom(double _zoom); 36 | // 重写鼠标事件 37 | void wheelEvent(QWheelEvent* ev); 38 | void mouseMoveEvent(QMouseEvent* ev); 39 | void mousePressEvent(QMouseEvent* ev); 40 | void mouseReleaseEvent(QMouseEvent* ev); 41 | // 重写变换方法,发送信号 42 | void scale(qreal sx, qreal sy); 43 | void translate(qreal dx, qreal dy); 44 | 45 | signals: 46 | void zoomRequest(double zoomAll); 47 | void mousePosChanged(double x, double y); 48 | // 同步信号给另一个Canvas 49 | void syncRequest(int hPos, int vPos, QTransform tf, double zoom); 50 | }; 51 | -------------------------------------------------------------------------------- /labcd/widgets/canvas.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "canvas.h" 8 | #include "labpolygon.h" 9 | 10 | Canvas::Canvas(QWidget* parent) 11 | : QScrollArea(parent) 12 | { 13 | setWidgetResizable(true); 14 | // 绘图区 15 | aScene = new AnnotationScence(); 16 | aView = new AnnotationView(aScene, parent); 17 | QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 18 | aView->setSizePolicy(sizePolicy); 19 | aView->setAlignment(Qt::AlignCenter); 20 | aView->setAutoFillBackground(false); 21 | aView->setStyleSheet("background-color: White"); 22 | connect(aView, &AnnotationView::mousePosChanged, 23 | aScene, &AnnotationScence::onMouseChanged); 24 | // 保持滑动滑块的时候也能同步 25 | connect(aView->horizontalScrollBar(), &QScrollBar::valueChanged, [=](int value) { 26 | emit syncScroll(value, aView->verticalScrollBar()->value());}); 27 | connect(aView->verticalScrollBar(), &QScrollBar::valueChanged, [=](int value) { 28 | emit syncScroll(aView->horizontalScrollBar()->value(), value);}); 29 | // 加载 30 | setWidget(aView); 31 | } 32 | 33 | Canvas::~Canvas() 34 | { 35 | 36 | } 37 | 38 | void Canvas::resetZoom(int width, int height) 39 | { 40 | aScene->setSceneRect(0, 0, width, height); 41 | // 缩放清除 42 | aView->scale(1 / aView->getZoomAll(), 1 / aView->getZoomAll()); // 重置缩放 43 | aView->setZoomAll(1); 44 | // 最佳缩放 45 | double scrContWidth = (this->width() * optSize) / width; 46 | double scrContHeight = (this->height() * optSize) / height; 47 | if (scrContWidth * height > this->height()) 48 | aView->setZoomAll(scrContHeight); 49 | else 50 | aView->setZoomAll(scrContWidth); 51 | aView->scale(aView->getZoomAll(), aView->getZoomAll()); 52 | aScene->setScaleRate(aView->getZoomAll()); 53 | } 54 | 55 | void Canvas::loadImageFromPixmap(QPixmap pixmap) 56 | { 57 | aScene->removeAllPolygons(); 58 | aScene->clear(); 59 | Canvas::resetZoom(pixmap.width(), pixmap.height()); 60 | QGraphicsPixmapItem* pixmapItem = new QGraphicsPixmapItem(); 61 | pixmapItem->setPixmap(pixmap); 62 | aScene->addItem(pixmapItem); 63 | } 64 | 65 | void Canvas::loadJSONFromFile(QString jsonPath) 66 | { 67 | std::ifstream ifs(jsonPath.toStdString(), std::ios::binary); 68 | if (!ifs.is_open()) 69 | return; 70 | Json::Reader reader; 71 | Json::Value root; 72 | // 解析json内容 73 | if (reader.parse(ifs, root)) 74 | { 75 | for (int i = 0; i < root.size(); ++i) 76 | { 77 | QColor jColor = QColor( 78 | root[i]["color"]["R"].asInt(), 79 | root[i]["color"]["G"].asInt(), 80 | root[i]["color"]["B"].asInt() 81 | ); 82 | int jIndex = root[i]["labelIndex"].asInt(); 83 | emit addJsonPoly(jIndex, jColor); 84 | int jPointNumber = root[i]["polygon"]["pointNumber"].asInt(); 85 | // 新建多边形 86 | LabPolygon* nowItem = new LabPolygon( 87 | aScene, i, jIndex, aScene->imgWidth, aScene->imgHeight, 88 | jColor, jColor, aScene->opacity 89 | ); 90 | aScene->addItem(nowItem); 91 | aScene->polygonItems.push_back(nowItem); 92 | // 添加点 93 | for (int j = 0; j < jPointNumber; ++j) 94 | { 95 | QPointF* point = new QPointF( 96 | root[i]["polygon"]["points"][2 * j].asFloat(), 97 | root[i]["polygon"]["points"][2 * j + 1].asFloat() 98 | ); 99 | nowItem->addPointLast(*point); 100 | } 101 | } 102 | } 103 | ifs.close(); 104 | } 105 | 106 | void Canvas::scroolTranslate(int hPos, int vPos) 107 | { 108 | aView->horizontalScrollBar()->setValue(hPos); 109 | aView->verticalScrollBar()->setValue(vPos); 110 | } 111 | -------------------------------------------------------------------------------- /labcd/widgets/canvas.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "annotationview.h" 7 | #include "annotationscence.h" 8 | #include "../utils/label.h" 9 | 10 | class Canvas : public QScrollArea 11 | { 12 | Q_OBJECT 13 | 14 | private: 15 | double optSize = 0.98; // 最佳缩放 16 | 17 | 18 | public: 19 | AnnotationView* aView = nullptr; 20 | AnnotationScence* aScene = nullptr; 21 | 22 | Canvas(QWidget* parent = nullptr); 23 | ~Canvas(); 24 | void resetZoom(int width, int height); 25 | void loadImageFromPixmap(QPixmap pixmap); 26 | void loadJSONFromFile(QString jsonPath); 27 | void scroolTranslate(int hPos, int vPos); 28 | 29 | signals: 30 | void syncScroll(int hPos, int vPos); // 滑块控制 31 | void addJsonPoly(int index, QColor color); 32 | }; 33 | -------------------------------------------------------------------------------- /labcd/widgets/filelist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "filelist.h" 5 | 6 | FileList::FileList(QWidget* parent) 7 | : QWidget(parent) 8 | { 9 | // 布局 10 | QVBoxLayout* vLayout = new QVBoxLayout(this); 11 | fList = new QListWidget(this); 12 | connect(fList, &QListWidget::itemDoubleClicked, [=](QListWidgetItem* item) { 13 | emit saveLastFileRequest(); // 发送信号,保存上一张图像 14 | gotoItem(item); // 发送信号,当前点击的文件名打开图像 15 | }); 16 | vLayout->addWidget(fList); 17 | progressBar = new QProgressBar(this); 18 | progressBar->setValue(0); 19 | progressBar->setFormat("0 / 0"); 20 | progressBar->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); // 对齐方式 21 | vLayout->addWidget(progressBar); 22 | // 加载 23 | setLayout(vLayout); 24 | } 25 | 26 | FileList::~FileList() 27 | { 28 | 29 | } 30 | 31 | QString FileList::getGTJsonPath(QString t1Path) 32 | { 33 | QFileInfo fileInfo(t1Path); 34 | QString jsonPath = t1Path.replace("A", "GT"); 35 | jsonPath = jsonPath.replace(fileInfo.completeSuffix(), "json"); 36 | QFileInfo jsonInfo(jsonPath); 37 | if (jsonInfo.isFile()) 38 | return jsonPath; 39 | else 40 | return ""; 41 | } 42 | 43 | void FileList::addFileNames(QStringList t1List, QStringList t2List) 44 | { 45 | QStringList t1Files = t1List; 46 | t2Files = t2List; 47 | // 显示 48 | fList->addItems(t1Files); 49 | fList->setCurrentRow(t1Files.size() - 1); // 移动位置 50 | } 51 | 52 | bool FileList::gotoItem(int index) 53 | { 54 | if (index < 0 || index > fList->count() - 1) 55 | return false; 56 | fList->setCurrentRow(index); 57 | nowIndex = index; 58 | QString t1Path = fList->currentItem()->text(); 59 | QString t2Path = t2Files.at(fList->currentRow()); 60 | emit FileList::FileClickRequest(t1Path, t2Path, getGTJsonPath(t1Path)); 61 | return true; 62 | } 63 | 64 | bool FileList::gotoItem(QListWidgetItem* item) 65 | { 66 | try 67 | { 68 | fList->setCurrentItem(item); 69 | nowIndex = fList->currentRow(); 70 | QString t1Path = item->text(); 71 | QString t2Path = t2Files.at(fList->currentRow()); 72 | emit FileList::FileClickRequest(t1Path, t2Path, getGTJsonPath(t1Path)); 73 | return true; 74 | } 75 | catch (...) 76 | { 77 | return false; 78 | } 79 | } 80 | 81 | bool FileList::gotoLastItem() 82 | { 83 | return gotoItem(fList->currentIndex().row() - 1); 84 | } 85 | 86 | bool FileList::gotoNextItem() 87 | { 88 | return gotoItem(fList->currentIndex().row() + 1); 89 | } 90 | 91 | void FileList::finishedCurrentItem() 92 | { 93 | QListWidgetItem* item = fList->item(nowIndex); 94 | if (item->background().color() != QColor("#7fffd4")) 95 | { 96 | item->setBackground(QBrush(QColor("#7fffd4"))); 97 | finishedNumber++; 98 | } 99 | } 100 | 101 | void FileList::resetProgress() 102 | { 103 | progressBar->setRange(0, fList->count()); 104 | progressBar->reset(); 105 | finishedNumber = 0; 106 | progressBar->setValue(0); 107 | progressBar->setFormat("0 / %m"); 108 | } 109 | 110 | void FileList::progressUpAdd() 111 | { 112 | progressBar->setValue(finishedNumber); 113 | progressBar->setFormat(QString::number(finishedNumber) + " / %m"); 114 | } 115 | -------------------------------------------------------------------------------- /labcd/widgets/filelist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class FileList : public QWidget 8 | { 9 | Q_OBJECT 10 | 11 | private: 12 | QListWidget* fList = nullptr; // t1Files 13 | QProgressBar* progressBar = nullptr; // 进度条 14 | QStringList t2Files; 15 | int nowIndex = 0; 16 | int finishedNumber = 0; 17 | 18 | QString getGTJsonPath(QString t1Path); 19 | 20 | public: 21 | FileList(QWidget* parent = nullptr); 22 | ~FileList(); 23 | void addFileNames(QStringList t1List, QStringList t2List); 24 | bool gotoItem(int index); 25 | bool gotoItem(QListWidgetItem* item); 26 | bool gotoLastItem(); 27 | bool gotoNextItem(); 28 | void finishedCurrentItem(); 29 | void resetProgress(); 30 | void progressUpAdd(); 31 | 32 | signals: 33 | void FileClickRequest(QString t1Path, QString t2Path, QString jsonPath); 34 | void saveLastFileRequest(); 35 | }; 36 | -------------------------------------------------------------------------------- /labcd/widgets/labeltable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "labeltable.h" 10 | 11 | LabelTable::LabelTable(QWidget* parent) 12 | : QWidget(parent) 13 | { 14 | nowLabel = new Label(); 15 | // 颜色表 16 | cMap = new ColorMap(); 17 | // 布局 18 | QVBoxLayout* vLayout = new QVBoxLayout(this); 19 | // 标签列表 20 | labelTable = new QTableWidget(this); 21 | labelTable->setRowCount(0); 22 | labelTable->setColumnCount(4); 23 | labelTable->setColumnWidth(2, 50); 24 | auto labTabHHeader = labelTable->horizontalHeader(); 25 | labTabHHeader->hide(); 26 | labTabHHeader->setSectionResizeMode(QHeaderView::Stretch); //铺满 27 | // 仅文字框宽度自适应 28 | labTabHHeader->setDefaultSectionSize(30); 29 | labTabHHeader->setSectionResizeMode(0, QHeaderView::Fixed); 30 | labTabHHeader->setSectionResizeMode(2, QHeaderView::Fixed); 31 | labTabHHeader->setSectionResizeMode(3, QHeaderView::Fixed); 32 | auto labTabVHeader = labelTable->verticalHeader(); 33 | labTabVHeader->hide(); 34 | // 选择、切换颜色和删除 35 | connect(labelTable, &QTableWidget::cellClicked, this, &LabelTable::clickItem); 36 | // 标签改变文本 37 | connect(labelTable, &QTableWidget::cellChanged, [=](int row, int column) { 38 | if (column == 1) 39 | nowLabel->setName(labelTable->item(row, 1)->text()); 40 | }); 41 | // 初始标签 42 | addLabelItem(true); 43 | vLayout->addWidget(labelTable); 44 | // 添加标签按钮 45 | QPushButton* btnAddLabel = new QPushButton(tr("添加标签"), this); 46 | btnAddLabel->setIcon(QIcon(":/docks/resources/AddLabel.png")); 47 | connect(btnAddLabel, &QPushButton::clicked, this, &LabelTable::addLabelItem); 48 | vLayout->addWidget(btnAddLabel); 49 | // 加载 50 | setLayout(vLayout); 51 | } 52 | 53 | LabelTable::~LabelTable() 54 | { 55 | 56 | } 57 | 58 | void LabelTable::clickItem(int row, int column) 59 | { 60 | int nowIndex = labelTable->item(row, 0)->text().toInt(); 61 | QString nowName = labelTable->item(row, 1)->text(); 62 | QColor nowColor = labelTable->item(row, 2)->background().color(); 63 | nowLabel->reSet(nowIndex, nowName, nowColor); 64 | if (column == 2 && row != 0) // 换颜色 65 | { 66 | QColor newColor = QColorDialog::getColor( 67 | nowColor, this, tr("标签颜色选择")); 68 | if (newColor.isValid()) 69 | { 70 | labelTable->item(row, 2)->setBackground(newColor); 71 | nowLabel->setColor(newColor); 72 | emit colorChanged(row, newColor); 73 | } 74 | } 75 | else if (column == 3 && row != 0) // 删除 76 | { 77 | if (nowLabel->getIndex() == row) 78 | nowLabel->init(); 79 | labelTable->removeRow(row); 80 | for (int r = 0; r < labelTable->rowCount(); ++r) 81 | { 82 | labelTable->item(r, 0)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable); 83 | labelTable->item(r, 0)->setText(QString::number(r)); 84 | labelTable->item(r, 0)->setFlags( 85 | labelTable->item(r, 0)->flags() & ~Qt::ItemIsEditable 86 | ); 87 | } 88 | } 89 | else // 点击特效 90 | { 91 | for (int col = 0; col < 2; col++) 92 | { 93 | for (int r = 0; r < labelTable->rowCount(); ++r) 94 | labelTable->item(r, col)->setBackground( 95 | QColor(255, 255, 255)); 96 | labelTable->item(row, col)->setBackground( 97 | QColor(48, 140, 198)); 98 | labelTable->item(row, 0)->setSelected(true); 99 | } 100 | emit labelSelected(nowLabel); 101 | } 102 | } 103 | 104 | void LabelTable::createLabelItem(int _index, QString _name, QColor _color) 105 | { 106 | labelTable->insertRow(labelTable->rowCount()); // 插入行 107 | QTableWidgetItem* indexItem = new QTableWidgetItem(); 108 | indexItem->setText(QString::number(_index)); 109 | indexItem->setTextAlignment(Qt::AlignCenter); 110 | indexItem->setFlags(indexItem->flags() & ~Qt::ItemIsEditable); 111 | labelTable->setItem(_index, 0, indexItem); // 序号 112 | QTableWidgetItem* nameItem = new QTableWidgetItem(); 113 | nameItem->setText(_name); 114 | nameItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable); 115 | labelTable->setItem(_index, 1, nameItem); // 名称 116 | QTableWidgetItem* colorItem = new QTableWidgetItem(); 117 | colorItem->setBackground(_color); 118 | colorItem->setFlags(Qt::ItemIsEnabled); 119 | labelTable->setItem(_index, 2, colorItem); // 颜色 120 | QTableWidgetItem* delItem = new QTableWidgetItem(); 121 | delItem->setIcon(QIcon(":/docks/resources/Delete.png")); 122 | delItem->setFlags(Qt::ItemIsEnabled); 123 | labelTable->setItem(_index, 3, delItem); // 删除按钮 124 | } 125 | 126 | int LabelTable::getLen() 127 | { 128 | return labelTable->rowCount(); 129 | } 130 | 131 | QColor LabelTable::getColorByIndex(int index) 132 | { 133 | if (index > labelTable->rowCount() - 1 || index < 0) 134 | exit(-1); 135 | return labelTable->item(index, 2)->background().color(); 136 | } 137 | 138 | void LabelTable::addLabelItem(bool init) 139 | { 140 | if (init) 141 | { 142 | createLabelItem(0, tr("背景"), QColor(0, 0, 0)); 143 | labelTable->item(0, 1)->setFlags( 144 | labelTable->item(0, 1)->flags() & ~Qt::ItemIsEditable); 145 | labelTable->item(0, 3)->setIcon(QIcon(":/docks/resources/CantDelete.png")); 146 | } 147 | else 148 | { 149 | int idx = labelTable->rowCount(); 150 | createLabelItem(idx, "", cMap->getColor()); 151 | } 152 | } 153 | 154 | void LabelTable::exportLabelToFile(QString path) 155 | { 156 | Json::StyledWriter writer; 157 | Json::Value root; 158 | std::ofstream os; 159 | os.open(path.toStdString()); 160 | // 写入json内容 161 | for (int i = 1; i < labelTable->rowCount(); ++i) 162 | { 163 | Json::Value leaf; 164 | leaf["index"] = labelTable->item(i, 0)->text().toInt(); 165 | leaf["classname"] = labelTable->item(i, 1)->text().toStdString(); 166 | QColor c = labelTable->item(i, 2)->background().color(); 167 | leaf["color"]["R"] = c.red(); 168 | leaf["color"]["G"] = c.green(); 169 | leaf["color"]["B"] = c.blue(); 170 | root.append(leaf); 171 | } 172 | os << writer.write(root); 173 | os.close(); 174 | } 175 | 176 | bool LabelTable::importLabelFromFile(QString path) 177 | { 178 | std::ifstream ifs(path.toStdString(), std::ios::binary); 179 | if (!ifs.is_open()) 180 | return false; 181 | Json::Reader reader; 182 | Json::Value root; 183 | // 解析json内容 184 | if (reader.parse(ifs, root)) 185 | { 186 | for (int i = 0; i < root.size(); i++) 187 | { 188 | int jIndex = root[i]["index"].asInt(); 189 | QString jClassName = QString(root[i]["classname"].asCString()); 190 | QColor jColor = QColor( 191 | root[i]["color"]["R"].asInt(), 192 | root[i]["color"]["G"].asInt(), 193 | root[i]["color"]["B"].asInt() 194 | ); 195 | createLabelItem(jIndex, jClassName, jColor); // 恢复标签 196 | } 197 | } 198 | ifs.close(); 199 | cMap->setIndex(root.size()); // 移动色表 200 | return true; 201 | } 202 | 203 | void LabelTable::changeLabelDuotoAddPolyJson(int index, QColor color) 204 | { 205 | if (index > labelTable->rowCount() - 1) 206 | { 207 | for (int i = 0; i < index - labelTable->rowCount() + 1; ++i) 208 | LabelTable::createLabelItem( 209 | labelTable->rowCount() + i, "", color); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /labcd/widgets/labeltable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../utils/label.h" 8 | #include "../utils/colormap.h" 9 | 10 | class LabelTable : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | private: 15 | QTableWidget* labelTable = nullptr; 16 | Label* nowLabel = nullptr; 17 | ColorMap* cMap = nullptr; 18 | 19 | void clickItem(int row, int column); 20 | void createLabelItem(int _index, QString _name, QColor _color); 21 | 22 | public: 23 | LabelTable(QWidget* parent = nullptr); 24 | ~LabelTable(); 25 | int getLen(); 26 | QColor getColorByIndex(int index); 27 | void addLabelItem(bool init = false); 28 | void exportLabelToFile(QString path); 29 | bool importLabelFromFile(QString path); 30 | void changeLabelDuotoAddPolyJson(int index, QColor color); 31 | 32 | signals: 33 | void labelSelected(Label* nowLabel); 34 | void colorChanged(int labelIndex, QColor newColor); 35 | }; 36 | -------------------------------------------------------------------------------- /labcd/widgets/labgrid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "labpolygon.h" 4 | #include "labgrid.h" 5 | #include "annotationscence.h" 6 | #include "opttypes.h" 7 | 8 | LabGrid::LabGrid( 9 | LabPolygon* _annItem, 10 | int _index, 11 | QColor _color, 12 | int _imgHeight, 13 | int _imgWidth 14 | ) 15 | { 16 | // 初始化 17 | annItem = _annItem; 18 | index = _index; 19 | color = _color; 20 | color.setAlphaF(1.0); 21 | imgHeight = _imgHeight; 22 | imgWidth = _imgWidth; 23 | // 设置 24 | updateSize(); 25 | setPath(circlePath); 26 | setBrush(color); 27 | setPen(QPen(color, 1)); 28 | setFlag(QGraphicsItem::ItemIsSelectable, true); 29 | setFlag(QGraphicsItem::ItemIsMovable, true); 30 | setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 31 | setFlag(QGraphicsItem::ItemIsFocusable, true); 32 | setAcceptHoverEvents(true); 33 | setZValue(12); 34 | setCursor(QCursor(Qt::PointingHandCursor)); 35 | } 36 | 37 | LabGrid::~LabGrid() 38 | { 39 | 40 | } 41 | 42 | void LabGrid::setColor(QColor c) 43 | { 44 | setBrush(c); 45 | setPen(QPen(c, 1)); 46 | color = c; 47 | } 48 | 49 | void LabGrid::updateSize(double s) 50 | { 51 | double size = minSize; 52 | circlePath.addEllipse(QRectF(-size, -size, size * s, size * s)); 53 | squarePath.addRect(QRectF(-size, -size, size * s, size * s)); 54 | if (hovering) 55 | setPath(squarePath); 56 | else 57 | setPath(circlePath); 58 | } 59 | 60 | void LabGrid::hoverEnterEvent(QGraphicsSceneHoverEvent* ev) 61 | { 62 | setPath(squarePath); 63 | setBrush(QColor(0, 0, 0, 0)); 64 | annItem->itemHovering = true; 65 | hovering = true; 66 | emit annItem->scene()->mouseOptRequest( 67 | annItem->index, index, OptTypes::GridHoverEnter, ev); 68 | QGraphicsPathItem::hoverEnterEvent(ev); 69 | } 70 | 71 | void LabGrid::hoverLeaveEvent(QGraphicsSceneHoverEvent* ev) 72 | { 73 | setPath(circlePath); 74 | setBrush(color); 75 | annItem->itemHovering = false; 76 | hovering = false; 77 | emit annItem->scene()->mouseOptRequest( 78 | annItem->index, index, OptTypes::GridHoverLeave, ev); 79 | QGraphicsPathItem::hoverLeaveEvent(ev); 80 | } 81 | 82 | void LabGrid::mousePressEvent(QGraphicsSceneMouseEvent* ev) 83 | { 84 | if (ev->button() == Qt::LeftButton) 85 | { 86 | annItem->scene()->clearSelection(); 87 | setSelected(true); 88 | emit annItem->scene()->mouseOptRequest( 89 | annItem->index, index, OptTypes::GridMousePress, ev); 90 | QGraphicsPathItem::mousePressEvent(ev); 91 | } 92 | } 93 | 94 | void LabGrid::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev) 95 | { 96 | setSelected(false); 97 | emit annItem->scene()->mouseOptRequest( 98 | annItem->index, index, OptTypes::GridMouseRelease, ev); 99 | QGraphicsPathItem::mouseReleaseEvent(ev); 100 | } 101 | 102 | void LabGrid::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev) 103 | { 104 | // 创建中无法删除 105 | if (!annItem->scene()->drawing) 106 | { 107 | emit annItem->scene()->mouseOptRequest( 108 | annItem->index, index, OptTypes::GridMouseDoubleClick, ev); 109 | annItem->removeFocusPoint(index); 110 | } 111 | } 112 | 113 | void LabGrid::mouseMoveEvent(QGraphicsSceneMouseEvent* ev) 114 | { 115 | emit annItem->scene()->mouseOptRequest( 116 | annItem->index, index, OptTypes::GridMouseMove, ev); 117 | QGraphicsPathItem::mouseMoveEvent(ev); 118 | } 119 | 120 | QVariant LabGrid::itemChange( 121 | GraphicsItemChange change, const QVariant& value 122 | ) 123 | { 124 | double x, y; 125 | QVariant tmpVal = QVariant(value); 126 | if (change == QGraphicsItem::ItemPositionChange && isEnabled()) 127 | { 128 | if (tmpVal.toPointF().x() > imgWidth) 129 | x = imgWidth; 130 | else if (tmpVal.toPointF().x() < 0) 131 | x = 0; 132 | else 133 | x = tmpVal.toPointF().x(); 134 | if (tmpVal.toPointF().y() > imgHeight) 135 | y = imgHeight; 136 | else if (tmpVal.toPointF().y() < 0) 137 | y = 0; 138 | else 139 | y = tmpVal.toPointF().y(); 140 | tmpVal = QPointF(x, y); 141 | annItem->movePoint(index, tmpVal.toPointF()); 142 | } 143 | return QGraphicsPathItem::itemChange(change, tmpVal); 144 | } 145 | 146 | QPainterPath LabGrid::shape() 147 | { 148 | QPainterPath newPath; 149 | QPointF p = mapFromScene(pos()); 150 | newPath.addEllipse(p, 2 * minSize, 2 * minSize); 151 | return newPath; 152 | } 153 | -------------------------------------------------------------------------------- /labcd/widgets/labgrid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class LabPolygon; 9 | 10 | class LabGrid : public QGraphicsPathItem 11 | { 12 | private: 13 | const double minSize = 0.8; 14 | const double maxSize = 1.5; 15 | QPainterPath circlePath; 16 | QPainterPath squarePath; 17 | 18 | public: 19 | LabPolygon* annItem; 20 | bool hovering = false; 21 | int index; 22 | QColor color; 23 | int imgHeight; 24 | int imgWidth; 25 | 26 | LabGrid( 27 | LabPolygon* _annItem, 28 | int _index, 29 | QColor _color, 30 | int _imgHeight, 31 | int _imgWidth 32 | ); 33 | ~LabGrid(); 34 | void setColor(QColor c); 35 | void updateSize(double s = 2.0); 36 | // 事件 37 | void hoverEnterEvent(QGraphicsSceneHoverEvent* ev); 38 | void hoverLeaveEvent(QGraphicsSceneHoverEvent* ev); 39 | void mousePressEvent(QGraphicsSceneMouseEvent* ev); 40 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* ev); 41 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev); 42 | void mouseMoveEvent(QGraphicsSceneMouseEvent* ev); 43 | // 重写方法 44 | QVariant itemChange( 45 | GraphicsItemChange change, const QVariant& value); 46 | QPainterPath shape(); 47 | }; 48 | -------------------------------------------------------------------------------- /labcd/widgets/labline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "labpolygon.h" 3 | #include "labline.h" 4 | #include "annotationscence.h" 5 | #include "opttypes.h" 6 | 7 | LabLine::LabLine( 8 | LabPolygon* _annItem, 9 | int _index, 10 | QColor _color 11 | ) 12 | { 13 | // 初始化 14 | annItem = _annItem; 15 | index = _index; 16 | color = _color; 17 | // 设置 18 | setPen(QPen(color, minWidth)); 19 | setZValue(11); 20 | setFlag(QGraphicsItem::ItemIsSelectable, true); 21 | setFlag(QGraphicsItem::ItemIsFocusable, true); 22 | setAcceptHoverEvents(true); 23 | setBoundingRegionGranularity(0.5); 24 | updateWidth(); 25 | } 26 | 27 | LabLine::~LabLine() 28 | { 29 | 30 | } 31 | 32 | void LabLine::setColor(QColor c) 33 | { 34 | setPen(QPen(color, minWidth)); 35 | color = c; 36 | updateWidth(); 37 | } 38 | 39 | void LabLine::updateWidth() 40 | { 41 | setPen(QPen(color, minWidth)); 42 | } 43 | 44 | QPolygonF LabLine::boundingPolygon() 45 | { 46 | double w = minWidth * 1.5; 47 | w = w < 2 ? w : 2; 48 | QPointF start = line().p1(); 49 | QPointF end = line().p2(); 50 | QPointF dir = start - end; 51 | double dx, dy; 52 | dx = -dir.y(); 53 | dy = dir.x(); 54 | double norm = sqrt(dx * dx + dy * dy); 55 | dx /= (norm + 1e-16); 56 | dy /= (norm + 1e-16); 57 | QPointF point(dx * w, dy * w); 58 | QList pointList = QList() << \ 59 | start - point << start + point << \ 60 | end + point << end - point; 61 | QPolygonF poly = QPolygonF(pointList); 62 | return poly; 63 | } 64 | 65 | void LabLine::hoverEnterEvent(QGraphicsSceneHoverEvent* ev) 66 | { 67 | boundingPolygon(); 68 | annItem->lineHovering = true; 69 | setPen(QPen(color, minWidth * 1.4)); 70 | emit annItem->scene()->mouseOptRequest( 71 | annItem->index, index, OptTypes::LineHoverEnter, ev); 72 | QGraphicsLineItem::hoverEnterEvent(ev); 73 | } 74 | 75 | void LabLine::hoverLeaveEvent(QGraphicsSceneHoverEvent* ev) 76 | { 77 | annItem->lineHovering = false; 78 | setPen(QPen(color, minWidth)); 79 | emit annItem->scene()->mouseOptRequest( 80 | annItem->index, index, OptTypes::LineHoverLeave, ev); 81 | QGraphicsLineItem::hoverLeaveEvent(ev); 82 | } 83 | 84 | void LabLine::mousePressEvent(QGraphicsSceneMouseEvent* ev) 85 | { 86 | if (ev->button() == Qt::LeftButton) 87 | { 88 | annItem->scene()->clearSelection(); 89 | setSelected(true); 90 | emit annItem->scene()->mouseOptRequest( 91 | annItem->index, index, OptTypes::LineMousePress, ev); 92 | QGraphicsLineItem::mousePressEvent(ev); 93 | } 94 | } 95 | 96 | void LabLine::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev) 97 | { 98 | setSelected(false); 99 | emit annItem->scene()->mouseOptRequest( 100 | annItem->index, index, OptTypes::LineMouseRelease, ev); 101 | QGraphicsLineItem::mouseReleaseEvent(ev); 102 | } 103 | 104 | void LabLine::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev) 105 | { 106 | setPen(QPen(color, minWidth)); 107 | annItem->addPointMiddle(index, ev->pos()); 108 | emit annItem->scene()->mouseOptRequest( 109 | annItem->index, index, OptTypes::LineMouseDoubleClick, ev); 110 | } 111 | 112 | QPainterPath LabLine::shape() 113 | { 114 | QPainterPath path; 115 | path.addPolygon(boundingPolygon()); 116 | return path; 117 | } 118 | -------------------------------------------------------------------------------- /labcd/widgets/labline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class LabPolygon; 9 | 10 | class LabLine : public QGraphicsLineItem 11 | { 12 | private: 13 | const double minWidth = 0.5; 14 | const double maxWidth = 1; 15 | 16 | public: 17 | LabPolygon* annItem; 18 | int index; 19 | QColor color; 20 | 21 | LabLine( 22 | LabPolygon* _annItem, 23 | int _index, 24 | QColor _color 25 | ); 26 | ~LabLine(); 27 | void setColor(QColor c); 28 | void updateWidth(); 29 | QPolygonF boundingPolygon(); 30 | // 事件 31 | void hoverEnterEvent(QGraphicsSceneHoverEvent* ev); 32 | void hoverLeaveEvent(QGraphicsSceneHoverEvent* ev); 33 | void mousePressEvent(QGraphicsSceneMouseEvent* ev); 34 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* ev); 35 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev); 36 | // 重写方法 37 | QPainterPath shape(); 38 | }; 39 | -------------------------------------------------------------------------------- /labcd/widgets/labpolygon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "annotationscence.h" 5 | #include "labpolygon.h" 6 | #include "opttypes.h" 7 | 8 | LabPolygon::LabPolygon( 9 | AnnotationScence* _nSence, 10 | int _index, 11 | int _labelIndex, 12 | int _imgWidth, 13 | int _imgHeight, 14 | QColor _insideColor, 15 | QColor _borderColor, 16 | double _opacity 17 | ) 18 | { 19 | // 初始化 20 | nSence = _nSence; 21 | index = _index; 22 | labelIndex = _labelIndex; 23 | imgWidth = _imgWidth; 24 | imgHeight = _imgHeight; 25 | insideColor = _insideColor; 26 | halfInsideColor = _insideColor; 27 | borderColor = _borderColor; 28 | opacity = _opacity; 29 | // 设置颜色 30 | setZValue(10); 31 | insideColor.setAlphaF(opacity); 32 | halfInsideColor.setAlphaF(opacity / 2); 33 | setBrush(halfInsideColor); 34 | borderColor.setAlphaF(0.8); 35 | setPen(QPen(borderColor)); 36 | setAcceptHoverEvents(true); 37 | // 其他设置 38 | setFlag(QGraphicsItem::ItemIsSelectable, true); 39 | setFlag(QGraphicsItem::ItemIsMovable, false); 40 | setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 41 | setFlag(QGraphicsItem::ItemIsFocusable, true); 42 | setCursor(QCursor(Qt::PointingHandCursor)); 43 | } 44 | 45 | LabPolygon::~LabPolygon() 46 | { 47 | 48 | } 49 | 50 | QList LabPolygon::getPointsNotPtr() 51 | { 52 | QList tmpPoints; 53 | for (QPointF* p : mPoints) 54 | tmpPoints.push_back(*p); 55 | return tmpPoints; 56 | } 57 | 58 | int LabPolygon::getLen() 59 | { 60 | return mPoints.count(); 61 | } 62 | 63 | int LabPolygon::getLabelIndex() 64 | { 65 | return labelIndex; 66 | } 67 | 68 | void LabPolygon::setColor(QColor _insideColor, QColor _borderColor) 69 | { 70 | insideColor = _insideColor; 71 | insideColor.setAlphaF(opacity); 72 | halfInsideColor = QColor(insideColor); 73 | halfInsideColor.setAlphaF(opacity / 2); 74 | setBrush(halfInsideColor); 75 | borderColor = _borderColor; 76 | borderColor.setAlphaF(0.8); 77 | setPen(QPen(borderColor)); 78 | for (LabGrid* item : mItems) 79 | item->setColor(borderColor); 80 | for (LabLine* line : mLines) 81 | line->setColor(borderColor); 82 | } 83 | 84 | QColor LabPolygon::getColor() 85 | { 86 | return borderColor; 87 | } 88 | 89 | QList LabPolygon::getScenePos() 90 | { 91 | QList points; 92 | for (QPointF* p : mPoints) 93 | { 94 | p = new QPointF(mapToScene(*p)); 95 | points.push_back(p); 96 | } 97 | return points; 98 | } 99 | 100 | void LabPolygon::addPointMiddle(int lineIndex, QPointF point) 101 | { 102 | // 添加点 103 | LabGrid* gripItem = new LabGrid( 104 | this, lineIndex + 1, borderColor, imgHeight, imgWidth); 105 | gripItem->setEnabled(false); 106 | gripItem->setPos(point); 107 | scene()->addItem(gripItem); 108 | gripItem->updateSize(); 109 | gripItem->setEnabled(true); 110 | for (int i = lineIndex + 1; i < mItems.count(); i++) 111 | mItems.at(i)->index += 1; 112 | mItems.insert(static_cast(lineIndex) + 1, gripItem); 113 | QPointF* gripPoint = new QPointF(mapFromScene(point)); 114 | mPoints.insert(static_cast(lineIndex) + 1, gripPoint); 115 | setPolygon(QPolygonF(getPointsNotPtr())); 116 | // 连线 117 | for (int i = lineIndex + 1; i < mLines.count(); i++) 118 | mLines.at(i)->index += 1; 119 | QLineF line1 = QLineF(mapToScene(*(mPoints.at(lineIndex))), point); 120 | mLines.at(lineIndex)->setLine(line1); 121 | LabLine* lineItem = new LabLine(this, lineIndex + 1, borderColor); 122 | QLineF line2 = QLineF( 123 | point, 124 | mapToScene(*(mPoints.at((lineIndex + 2) % getLen()))) 125 | ); 126 | lineItem->setLine(line2); 127 | mLines.insert(static_cast(lineIndex) + 1, lineItem); 128 | scene()->addItem(lineItem); 129 | lineItem->updateWidth(); 130 | } 131 | 132 | void LabPolygon::addPointLast(QPointF point) 133 | { 134 | LabGrid* grip = new LabGrid(this, getLen(), borderColor, imgHeight, imgWidth); 135 | scene()->addItem(grip); 136 | mItems.push_back(grip); 137 | grip->updateSize(); 138 | grip->setPos(point); 139 | if (getLen() == 0) 140 | { 141 | LabLine* line = new LabLine(this, getLen(), borderColor); 142 | scene()->addItem(line); 143 | mLines.push_back(line); 144 | line->setLine(QLineF()); 145 | } 146 | else 147 | { 148 | mLines.at(mLines.count() - 1)->setLine( 149 | QLineF(*mPoints.at(mPoints.count() - 1), point)); 150 | LabLine* line = new LabLine(this, getLen(), borderColor); 151 | scene()->addItem(line); 152 | mLines.push_back(line); 153 | line->setLine(QLineF(point, *mPoints.at(0))); 154 | } 155 | QPointF* nPoint = new QPointF(point); 156 | mPoints.push_back(nPoint); 157 | setPolygon(QPolygonF(getPointsNotPtr())); 158 | } 159 | 160 | void LabPolygon::remove() 161 | { 162 | for (LabGrid* grip : mItems) 163 | scene()->removeItem(grip); 164 | for (LabLine* line : mLines) 165 | scene()->removeItem(line); 166 | while (mItems.count() != 0) 167 | mItems.pop_back(); 168 | while (mLines.count() != 0) 169 | mLines.pop_back(); 170 | scene()->polygonItems.removeAll(this); 171 | // 重排序 172 | for (int i = 0; i < scene()->polygonItems.count(); i++) 173 | scene()->polygonItems[i]->index = i; 174 | scene()->removeItem(this); 175 | delete this; 176 | } 177 | 178 | void LabPolygon::removeFocusPoint(int preFocusIndex) 179 | { 180 | int focusIndex = -1; 181 | if (preFocusIndex == -1) 182 | { 183 | for (int i = 0; i < mItems.count(); ++i) 184 | { 185 | if (mItems.at(i)->hasFocus()) 186 | { 187 | focusIndex = i; 188 | break; 189 | } 190 | } 191 | } 192 | else 193 | focusIndex = preFocusIndex; 194 | if (focusIndex != -1) 195 | { 196 | if (getLen() <= 3) 197 | remove(); 198 | else 199 | { 200 | mPoints.removeAt(focusIndex); 201 | setPolygon(QPolygonF(getPointsNotPtr())); 202 | scene()->removeItem(mItems.at(focusIndex)); 203 | mItems.removeAt(focusIndex); 204 | for (int i = focusIndex; i < mItems.count(); ++i) 205 | { 206 | mItems.at(i)->index -= 1; 207 | if (mItems.at(i)->index < 0) 208 | mItems.at(i)->index += mItems.count(); 209 | } 210 | scene()->removeItem(mLines.at(focusIndex)); 211 | mLines.removeAt(focusIndex); 212 | int lastIndex = focusIndex - 1; 213 | if (lastIndex < 0) 214 | lastIndex += mItems.count(); 215 | QLineF line( 216 | mapToScene(*mPoints.at(lastIndex % getLen())), 217 | mapToScene(*mPoints.at(focusIndex % getLen())) 218 | ); 219 | mLines.at(lastIndex % getLen())->setLine(line); 220 | for (int i = focusIndex; i < mLines.count(); ++i) 221 | { 222 | mLines.at(i)->index -= 1; 223 | if (mLines.at(i)->index < 0) 224 | mLines.at(i)->index += mLines.count(); 225 | } 226 | } 227 | } 228 | } 229 | 230 | void LabPolygon::movePoint(int index, QPointF point) 231 | { 232 | if (0 <= index && index < mPoints.count()) 233 | { 234 | mPoints[index] = new QPointF(mapFromScene(point)); 235 | setPolygon(QPolygonF(getPointsNotPtr())); 236 | moveLine(index); 237 | } 238 | } 239 | 240 | void LabPolygon::moveLine(int index) 241 | { 242 | if (!noMove) 243 | { 244 | QLineF line1 = QLineF( 245 | mapToScene(*mPoints.at(index)), 246 | mapToScene(*mPoints.at((index + 1) % getLen())) 247 | ); 248 | mLines[index]->setLine(line1); 249 | int lastIndex = index - 1; 250 | if (lastIndex < 0) 251 | lastIndex += mPoints.count(); 252 | QLineF line2 = QLineF( 253 | mapToScene(*mPoints.at(lastIndex % getLen())), 254 | mapToScene(*mPoints.at(index)) 255 | ); 256 | mLines[lastIndex % getLen()]->setLine(line2); 257 | } 258 | } 259 | 260 | void LabPolygon::moveItem(int index, QPointF point) 261 | { 262 | if (0 <= index && index < mItems.count()) 263 | { 264 | mItems[index]->setEnabled(false); 265 | mItems[index]->setPos(point); 266 | mItems[index]->setEnabled(true); 267 | moveLine(index); 268 | } 269 | } 270 | 271 | // hover指鼠标放在多边形上 272 | void LabPolygon::hoverEnterEvent(QGraphicsSceneHoverEvent* ev) 273 | { 274 | if (scene()->drawing) 275 | return; 276 | polyHovering = true; 277 | scene()->clearAllFocus(); 278 | setBrush(insideColor); 279 | emit scene()->mouseOptRequest(index, -1, OptTypes::PolyHoverEnter, ev); 280 | QGraphicsPolygonItem::hoverEnterEvent(ev); 281 | } 282 | 283 | void LabPolygon::hoverLeaveEvent(QGraphicsSceneHoverEvent* ev) 284 | { 285 | if (scene()->drawing) 286 | return; 287 | polyHovering = false; 288 | if (!hasFocus()) 289 | setBrush(halfInsideColor); 290 | emit scene()->mouseOptRequest(index, -1, OptTypes::PolyHoverLeave, ev); 291 | QGraphicsPolygonItem::hoverLeaveEvent(ev); 292 | 293 | } 294 | 295 | void LabPolygon::mousePressEvent(QGraphicsSceneMouseEvent* ev) 296 | { 297 | scene()->clearFocusAndSelected(); 298 | if (scene()->drawing) 299 | return; 300 | if (ev->button() == Qt::LeftButton) 301 | { 302 | setSelected(true); 303 | setFocus(); 304 | } 305 | emit scene()->mouseOptRequest(index, -1, OptTypes::PolyMousePress, ev); 306 | QGraphicsPolygonItem::mousePressEvent(ev); 307 | } 308 | 309 | void LabPolygon::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev) 310 | { 311 | setSelected(false); 312 | emit scene()->mouseOptRequest(index, -1, OptTypes::PolyMouseRelease, ev); 313 | QGraphicsPolygonItem::mouseReleaseEvent(ev); 314 | } 315 | 316 | // focus指鼠标点击多边形上 317 | void LabPolygon::focusInEvent(QFocusEvent* ev) 318 | { 319 | if (scene()->drawing) 320 | return; 321 | setBrush(insideColor); 322 | emit scene()->mouseOptRequest(index, -1, OptTypes::PolyFocusIn, ev); 323 | } 324 | 325 | void LabPolygon::focusOutEvent(QFocusEvent* ev) 326 | { 327 | if (scene()->drawing) 328 | return; 329 | if (!polyHovering) 330 | setBrush(halfInsideColor); 331 | emit scene()->mouseOptRequest(index, -1, OptTypes::PolyFocusOut, ev); 332 | } 333 | 334 | QVariant LabPolygon::itemChange( 335 | GraphicsItemChange change, const QVariant& value) 336 | { 337 | if (change == QGraphicsItem::ItemPositionHasChanged) 338 | { 339 | for (int i = 0; i < mPoints.count(); i++) 340 | moveItem(i, mapToScene(*(mPoints.at(i)))); 341 | } 342 | return QGraphicsPolygonItem::itemChange(change, value); 343 | } 344 | 345 | AnnotationScence* LabPolygon::scene() 346 | { 347 | return nSence; 348 | } 349 | -------------------------------------------------------------------------------- /labcd/widgets/labpolygon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "labline.h" 7 | #include "labgrid.h" 8 | 9 | class AnnotationScence; 10 | 11 | class LabPolygon : public QGraphicsPolygonItem 12 | { 13 | private: 14 | AnnotationScence* nSence; 15 | double opacity = 0.5; 16 | QColor insideColor = QColor(255, 0, 0); 17 | QColor halfInsideColor = QColor(255, 0, 0); 18 | QColor borderColor = QColor(0, 255, 0); 19 | 20 | QList getPointsNotPtr(); // 将点的指针的list转为点的list 21 | 22 | public: 23 | QList mPoints; 24 | QList mItems; 25 | QList mLines; 26 | int index; 27 | int labelIndex; 28 | int imgWidth; 29 | int imgHeight; 30 | bool itemHovering = false; 31 | bool lineHovering = false; 32 | bool polyHovering = false; 33 | bool noMove = false; 34 | bool lastFocse = false; 35 | 36 | LabPolygon( 37 | AnnotationScence* _nSence, 38 | int _index, 39 | int _labelIndex, 40 | int _imgWidth, 41 | int _imgHeight, 42 | QColor _insideColor = QColor(255, 0, 0), 43 | QColor _borderColor = QColor(0, 255, 0), 44 | double _opacity = 0.5 45 | ); 46 | ~LabPolygon(); 47 | int getLen(); 48 | int getLabelIndex(); 49 | void setColor(QColor _insideColor, QColor _borderColor); 50 | QColor getColor(); 51 | QList getScenePos(); 52 | void addPointMiddle(int lineIndex, QPointF point); 53 | void addPointLast(QPointF point); 54 | void remove(); 55 | void removeFocusPoint(int preFocusIndex = -1); 56 | void movePoint(int index, QPointF point); 57 | void moveLine(int index); 58 | void moveItem(int index, QPointF point); 59 | // 事件 60 | void hoverEnterEvent(QGraphicsSceneHoverEvent* ev); 61 | void hoverLeaveEvent(QGraphicsSceneHoverEvent* ev); 62 | void mousePressEvent(QGraphicsSceneMouseEvent* ev); 63 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* ev); 64 | void focusInEvent(QFocusEvent* ev); 65 | void focusOutEvent(QFocusEvent* ev); 66 | // 重写 67 | QVariant itemChange( 68 | GraphicsItemChange change, const QVariant& value); 69 | AnnotationScence* scene(); 70 | }; 71 | -------------------------------------------------------------------------------- /labcd/widgets/multcanvas.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "multcanvas.h" 5 | #include "../utils/imgpress.h" 6 | 7 | MultCanvas::MultCanvas(QWidget* parent) 8 | : QWidget(parent) 9 | { 10 | QGridLayout* gLayout = new QGridLayout(this); 11 | t1Canva = new Canvas(this); 12 | gLayout->addWidget(t1Canva, 0, 0); 13 | t2Canva = new Canvas(this); 14 | gLayout->addWidget(t2Canva, 0, 1); 15 | /* 操作关联 */ 16 | // 同步鼠标移动缩放 17 | connect( 18 | t1Canva->aView, &AnnotationView::syncRequest, 19 | t2Canva->aView, &AnnotationView::syncTranslate 20 | ); 21 | connect( 22 | t2Canva->aView, &AnnotationView::syncRequest, 23 | t1Canva->aView, &AnnotationView::syncTranslate 24 | ); 25 | // 同步垂直和水平滑动条位置 26 | connect(t1Canva, &Canvas::syncScroll, t2Canva, &Canvas::scroolTranslate); 27 | connect(t2Canva, &Canvas::syncScroll, t1Canva, &Canvas::scroolTranslate); 28 | // 加载标签和图像大小的同步 29 | connect(this, &MultCanvas::labelSelected, [=](Label* label) { 30 | t1Canva->aScene->getLabel(label); 31 | t2Canva->aScene->getLabel(label); 32 | }); 33 | connect(this, &MultCanvas::imageLoaded, [=](int imgWidth, int imgHeight) { 34 | t1Canva->aScene->getImageSize(imgWidth, imgHeight); 35 | t2Canva->aScene->getImageSize(imgWidth, imgHeight); 36 | }); 37 | // 同步画图 38 | connect( 39 | t1Canva->aScene, &AnnotationScence::mouseOptRequest, 40 | t2Canva->aScene, &AnnotationScence::copyMouseOpt 41 | ); 42 | connect( 43 | t2Canva->aScene, &AnnotationScence::mouseOptRequest, 44 | t1Canva->aScene, &AnnotationScence::copyMouseOpt 45 | ); 46 | // 同步十字丝 47 | connect( 48 | t1Canva->aView, &AnnotationView::mousePosChanged, 49 | t2Canva->aScene, &AnnotationScence::onMouseChanged 50 | ); 51 | connect( 52 | t2Canva->aView, &AnnotationView::mousePosChanged, 53 | t1Canva->aScene, &AnnotationScence::onMouseChanged 54 | ); 55 | setLayout(gLayout); 56 | } 57 | 58 | MultCanvas::~MultCanvas() 59 | { 60 | 61 | } 62 | 63 | void MultCanvas::loadImages(QString t1Path, QString t2Path, QString jsonPath) 64 | { 65 | QPixmap t1; 66 | QPixmap t2; 67 | bool t1Succ = ImagePress::openImage(t1Path, t1, projs, trans); 68 | bool t2Succ = ImagePress::openImage(t2Path, t2, projs, trans); 69 | if (!t1Succ || !t2Succ) 70 | QMessageBox::critical( 71 | this, 72 | tr("错误"), 73 | tr("无法打开图像文件。") 74 | ); 75 | else 76 | { 77 | if (t1.width() != t2.width() || t1.height() != t2.height()) 78 | QMessageBox::critical( 79 | this, 80 | tr("错误"), 81 | tr("两个时段的数据大小不一致。") 82 | ); 83 | else 84 | { 85 | t1Canva->loadImageFromPixmap(t1); 86 | t2Canva->loadImageFromPixmap(t2); 87 | imageWidth = t1.width(); 88 | imageHeight = t1.height(); 89 | emit imageLoaded(imageWidth, imageHeight); // 发送大小 90 | // 加载已标注过的数据 91 | if (jsonPath != "") 92 | { 93 | t1Canva->loadJSONFromFile(jsonPath); 94 | t2Canva->loadJSONFromFile(jsonPath); 95 | } 96 | t1Canva->aScene->resetScence(); 97 | t2Canva->aScene->resetScence(); 98 | // CVA变化图 99 | cv::Mat imgT1 = ImagePress::qpixmapToCVMat(t1); 100 | cv::Mat imgT2 = ImagePress::qpixmapToCVMat(t2); 101 | cv::Mat imgDiff = ImagePress::CVA(imgT1, imgT2); 102 | emit addimgDiff(imgDiff); 103 | } 104 | } 105 | } 106 | 107 | void MultCanvas::clearFocusAndSelected() 108 | { 109 | t1Canva->aScene->clearFocusAndSelected(); 110 | t2Canva->aScene->clearFocusAndSelected(); 111 | } 112 | 113 | void MultCanvas::finished() 114 | { 115 | t1Canva->aScene->finished(); 116 | t1Canva->aScene->finished(); 117 | } 118 | 119 | QColor MultCanvas::getCrossPenColor() 120 | { 121 | return t1Canva->aScene->getCrossPenColor(); 122 | } 123 | 124 | void MultCanvas::setCrossPenColor(QColor color) 125 | { 126 | t1Canva->aScene->setCrossPenColor(color); 127 | t2Canva->aScene->setCrossPenColor(color); 128 | } 129 | -------------------------------------------------------------------------------- /labcd/widgets/multcanvas.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "canvas.h" 6 | #include "../utils/label.h" 7 | 8 | class MultCanvas : public QWidget 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | Canvas* t1Canva = nullptr; 14 | Canvas* t2Canva = nullptr; 15 | int imageWidth = 0; 16 | int imageHeight = 0; 17 | std::string projs = ""; 18 | double trans[6] = { 0, 1, 0, 0, 0, 1 }; 19 | 20 | MultCanvas(QWidget* parent = nullptr); 21 | ~MultCanvas(); 22 | void loadImages(QString t1Path, QString t2Path, QString jsonPath); 23 | void clearFocusAndSelected(); 24 | void finished(); 25 | QColor getCrossPenColor(); 26 | void setCrossPenColor(QColor color); 27 | 28 | signals: 29 | void labelSelected(Label* label); 30 | void imageLoaded(int imgWidth, int imgHeight); 31 | void addimgDiff(cv::Mat imgDiff); 32 | }; 33 | -------------------------------------------------------------------------------- /labcd/widgets/opttypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class OptTypes 4 | { 5 | SceneMousePress, 6 | SceneMouseDoubleClick, 7 | PolyHoverEnter, 8 | PolyHoverLeave, 9 | PolyMousePress, 10 | PolyMouseRelease, 11 | PolyFocusIn, 12 | PolyFocusOut, 13 | LineHoverEnter, 14 | LineHoverLeave, 15 | LineMousePress, 16 | LineMouseRelease, 17 | LineMouseDoubleClick, 18 | GridHoverEnter, 19 | GridHoverLeave, 20 | GridMousePress, 21 | GridMouseRelease, 22 | GridMouseMove, 23 | GridMouseDoubleClick 24 | }; 25 | --------------------------------------------------------------------------------