Compare commits

...

36 Commits

Author SHA1 Message Date
al 9e0d7a34cd update README with win32 build info 12 months ago
al 773734e6be added crossfile for win32 12 months ago
al 45ef16522d add license headers to source 1 year ago
al 3775fcfb1a add license to README 1 year ago
al f4beff5fc9 add license 1 year ago
al 476cd5f28a move shared functionality to WorkerPool 1 year ago
al aec946622c reduce complexity by getting queue ptrs from worker pools 1 year ago
al 53978fe62c update buildsystem to allow for cross-compilation (win32) 1 year ago
al 609997f878 remove old filtering code 1 year ago
al d8a45f7e36 move assets -> data 1 year ago
al 25e9e79159 update buildsystem to reflect changes to directory structure 1 year ago
al d7a438b14c move source files to src/ 1 year ago
al 1a6f619db6 bump cairomm version 1 year ago
al c069025ee6 correct email in books.rc 1 year ago
al 5b9dbbb286 bump version to 0.2.2 1 year ago
al 9f177afc05 change progress window message to follow GNOME HIG 1 year ago
al 10e22bdfaf add rejection threshold control; change autocrop -> crop 1 year ago
al b12e6ef392 use gtksnapshot instead of passing visual offsets to draw funcs 1 year ago
al cea91493db use more appropriate icon for settings; change margins 1 year ago
al fea2b31c15 fix ui hang while re-importing images 1 year ago
al 6b1fd2f0e3 dilate kernel size > 0 to avoid breaking opencv 1 year ago
al 0baa99402d slim down win32 distributions by only including needed icons 1 year ago
al cefc4dc4c5 remove unused method 1 year ago
al d5703a23fd fix gtkoverlay again 1 year ago
al b1852b8259 fix gtkoverlay blocking mouse events 1 year ago
al fdfc884cf3 use G_PI 1 year ago
al 58422814e6 add cmath.h 1 year ago
al 6abd14cfa7 add prev/next image preview overlay 1 year ago
al 391d304fef fix margin button style 1 year ago
al f889f0dfc2 re-add show feature functionality 1 year ago
al b2f0db9b90 pass crop info to image exporter 1 year ago
al 6641250cc2 fix miscalculation of crop bounds 1 year ago
al 116efb461a construct box from Gdk::Rectangle 1 year ago
al ffeb1beb1d re-add cropping functionality to support custom tweaks 1 year ago
al fa8a5423ef implement manual tweaking 1 year ago
al 550db09287 use std::move to avoid copying shared_ptrs 1 year ago
  1. 1
      .gitignore
  2. 7
      LICENSE.md
  3. 69
      README.md
  4. 3
      assets/win32/make-icon.sh
  5. 19
      books.cpp
  6. 0
      data/icons/org.sugol.books-16.png
  7. 0
      data/icons/org.sugol.books-256.png
  8. 0
      data/icons/org.sugol.books-32.png
  9. 0
      data/icons/org.sugol.books.svg
  10. 0
      data/ui-resources.xml
  11. 0
      data/ui/MainWindow.glade
  12. 115
      data/ui/MainWindow.ui
  13. 0
      data/ui/ProgressWindow.ui
  14. 0
      data/win32/books.ico
  15. 6
      data/win32/books.rc
  16. 22
      data/win32/make-icon.sh
  17. 35
      featuredetector.hpp
  18. 18
      featuredetectorparams.hpp
  19. 80
      featuredetectorpool.cpp
  20. 38
      featuredetectorpool.hpp
  21. 13
      filter/bgr2grey.cpp
  22. 11
      filter/bgr2grey.hpp
  23. 25
      filter/canny.cpp
  24. 13
      filter/canny.hpp
  25. 17
      filter/dilate.cpp
  26. 15
      filter/dilate.hpp
  27. 11
      filter/filter.hpp
  28. 34
      filter/filterchain.cpp
  29. 19
      filter/filterchain.hpp
  30. 8
      filter/filters_all.hpp
  31. 16
      filter/medianblur.cpp
  32. 15
      filter/medianblur.hpp
  33. 13
      filter/normalise.cpp
  34. 11
      filter/normalise.hpp
  35. 11
      filter/threshold.cpp
  36. 11
      filter/threshold.hpp
  37. 16
      fitnessmetrics.hpp
  38. 59
      imageexporter.cpp
  39. 21
      imageexporter.hpp
  40. 68
      imageexporterpool.cpp
  41. 34
      imageexporterpool.hpp
  42. 27
      imageloader.cpp
  43. 14
      imageloader.hpp
  44. 71
      imageloaderpool.cpp
  45. 32
      imageloaderpool.hpp
  46. 112
      imagepreview.cpp
  47. 37
      imagepreview.hpp
  48. 37
      imagestore.cpp
  49. 21
      imagestore.hpp
  50. 54
      meson.build
  51. 28
      progresswindow.hpp
  52. 43
      scripts/dist-win32.sh
  53. 40
      src/books.cpp
  54. 70
      src/crophandle.cpp
  55. 95
      src/crophandle.hpp
  56. 106
      src/croprect.cpp
  57. 41
      src/croprect.hpp
  58. 35
      src/featuredetector.cpp
  59. 54
      src/featuredetector.hpp
  60. 39
      src/featuredetectorparams.hpp
  61. 44
      src/featuredetectorpool.cpp
  62. 42
      src/featuredetectorpool.hpp
  63. 21
      src/fitnessmetrics.cpp
  64. 37
      src/fitnessmetrics.hpp
  65. 30
      src/imagedata.cpp
  66. 34
      src/imagedata.hpp
  67. 66
      src/imageexporter.cpp
  68. 39
      src/imageexporter.hpp
  69. 38
      src/imageexporterpool.cpp
  70. 35
      src/imageexporterpool.hpp
  71. 44
      src/imageloader.cpp
  72. 32
      src/imageloader.hpp
  73. 39
      src/imageloaderpool.cpp
  74. 34
      src/imageloaderpool.hpp
  75. 301
      src/imagepreview.cpp
  76. 68
      src/imagepreview.hpp
  77. 58
      src/imagestore.cpp
  78. 42
      src/imagestore.hpp
  79. 144
      src/mainwindow.cpp
  80. 39
      src/mainwindow.hpp
  81. 36
      src/meson.build
  82. 24
      src/progresswindow.cpp
  83. 49
      src/progresswindow.hpp
  84. 40
      src/util/box.cpp
  85. 52
      src/util/box.hpp
  86. 41
      src/util/point.hpp
  87. 49
      src/worker.cpp
  88. 56
      src/worker.hpp
  89. 21
      src/workerpool.cpp
  90. 162
      src/workerpool.hpp
  91. 59
      src/workqueue.cpp
  92. 23
      src/workqueue.hpp
  93. 27
      util/box.hpp
  94. 20
      util/point.hpp
  95. 31
      win32-cross.txt
  96. 28
      worker.cpp
  97. 75
      worker.hpp
  98. 49
      workerpool.hpp
  99. 38
      workqueue.cpp

1
.gitignore vendored

@ -1,6 +1,5 @@
build/
builddir/
data/
output/
*.code-workspace
.cache

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

@ -1,8 +1,20 @@
# books
Automatic cropping tool for cropping to books
An intelligent bulk cropping tool
## Build
## Download
Linux and Windows are both supported.
Windows binaries are available [here](https://git.sugol.org/al/books/releases).
## Building from Source
### Dependencies
- opencv4
- gtkmm-4.0
- cairomm-1.16
- meson
- GCC or clang. C++11 support required
### Linux
@ -12,10 +24,59 @@ This project uses the meson build system:
$ meson build
$ cd build
$ ninja
```
And install with
```
$ sudo ninja install
```
### Windows
Since this project uses GTK4, it's a bit difficult here (there's no GTK4 installer for Win32 just yet). However, I have built using cygwin & it seems to work ok.
There are a couple of options for building Windows binaries. Either you can
compile natively using `msys2`, or cross-compile from Linux.
*MSVC is unsupported*
#### Native
Install `msys2` and build as you would on Linux
#### Cross-compilation
A cross-file is included for building Windows binaries on Linux
(`win32-cross.txt`). This file may need tweaking depending on how your `mingw`
installation is configured. Make sure to install the relevant mingw
dependencies.
Build as you would for Linux, but substitute the first step with `meson setup
build --cross-file=win32-cross.txt`.
Good luck!
#### Packaging
A script for packaging Windows builds is included under `scripts/dist-win32.sh`.
This should work with either `msys2` or a cross-compiled build, but you may need
to tweak the `MINGW` variable to match your mingw installation path.
The script will package the compiled executable, `.dll`s and icons into a zip
archive for distribution.
## License
Copyright © 2021 Alister Sanders (alister@sugol.org)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
TODO: Actual build instructions
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,3 +0,0 @@
#!/bin/bash
convert ../icons/org.sugol.books-16.png ../icons/org.sugol.books-32.png ../icons/org.sugol.books-256.png books.ico

@ -1,19 +0,0 @@
#include <gtkmm.h>
#include <mainwindow.hpp>
#include <ui-resources.h>
static void on_startup(Glib::RefPtr<Gtk::Application> app) {
ui::MainWindow* window = ui::MainWindow::create();
app->add_window(*window);
window->show();
}
int main(int argc, char *argv[]) {
auto resource_bundle = Glib::wrap(ui_res_get_resource());
resource_bundle->register_global();
auto app = Gtk::Application::create("org.sugol.books");
app->signal_startup().connect([&]() { on_startup(app); });
return app->run(argc, argv);
}

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 522 B

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -10,7 +10,7 @@
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkMenuButton">
<property name="icon-name">view-more-symbolic</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="popover">
<object class="GtkPopover">
<property name="width-request">300</property>
@ -18,10 +18,10 @@
<child>
<object class="GtkGrid">
<property name="can-focus">0</property>
<property name="margin-top">16</property>
<property name="margin-start">16</property>
<property name="margin-bottom">16</property>
<property name="margin-end">16</property>
<property name="margin-top">8</property>
<property name="margin-start">8</property>
<property name="margin-bottom">8</property>
<property name="margin-end">8</property>
<child>
<object class="GtkLabel">
<property name="label">Margins</property>
@ -35,18 +35,56 @@
</child>
<child>
<object class="GtkScale" id="scaleMargins">
<object class="GtkBox">
<style>
<class name="linked" />
</style>
<property name="halign">end</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkButton" id="btnMarginAdd">
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnMarginSubtract">
<property name="icon-name">list-remove-symbolic</property>
</object>
</child>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Rejection threshold</property>
<property name="halign">start</property>
<property name="hexpand">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkScale" id="scaleRejection">
<property name="adjustment">
<object class="GtkAdjustment">
<property name="lower">-50</property>
<property name="upper">200</property>
<property name="value">0</property>
<property name="lower">0</property>
<property name="upper">1</property>
<property name="value">0.1</property>
</object>
</property>
<property name="digits">2</property>
<property name="draw-value">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
@ -58,7 +96,7 @@
<property name="hexpand">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
@ -69,7 +107,7 @@
<property name="hexpand">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
@ -81,7 +119,7 @@
<property name="hexpand">1</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
<property name="row">3</property>
</layout>
</object>
</child>
@ -93,7 +131,7 @@
<property name="sensitive">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
<property name="row">3</property>
</layout>
</object>
</child>
@ -106,7 +144,7 @@
<property name="hexpand">1</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
<property name="row">4</property>
</layout>
</object>
</child>
@ -123,7 +161,7 @@
</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
<property name="row">4</property>
</layout>
</object>
</child>
@ -136,7 +174,7 @@
<property name="hexpand">1</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
<property name="row">5</property>
</layout>
</object>
</child>
@ -145,7 +183,7 @@
<object class="GtkSpinButton" id="spinDilateKernel">
<property name="adjustment">
<object class="GtkAdjustment">
<property name="lower">0</property>
<property name="lower">1</property>
<property name="upper">50</property>
<property name="step-increment">1</property>
<property name="value">16</property>
@ -153,7 +191,7 @@
</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
<property name="row">5</property>
</layout>
</object>
</child>
@ -166,7 +204,7 @@
<property name="hexpand">1</property>
<layout>
<property name="column">0</property>
<property name="row">5</property>
<property name="row">6</property>
</layout>
</object>
</child>
@ -183,7 +221,7 @@
</property>
<layout>
<property name="column">1</property>
<property name="row">5</property>
<property name="row">6</property>
</layout>
</object>
</child>
@ -235,14 +273,39 @@
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width-request">800</property>
<object class="GtkBox">
<property name="hexpand">1</property>
<property name="width-request">300</property>
<property name="height-request">200</property>
<child>
<object class="GtkViewport">
<property name="can-focus">0</property>
<object class="GtkOverlay">
<property name="hexpand">1</property>
<child type="overlay">
<object class="GtkButton" id="btnPrevImage">
<property name="halign">start</property>
<property name="valign">center</property>
<property name="icon-name">go-previous-symbolic</property>
<style>
<class name="flat" />
</style>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="btnNextImage">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="icon-name">go-next-symbolic</property>
<style>
<class name="flat" />
</style>
</object>
</child>
<child>
<object class="GtkDrawingArea" id="preview">
<object class="GtkPicture" id="preview">
<property name="can-focus">0</property>
<property name="halign">center</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
</object>
</child>
</object>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

@ -8,12 +8,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Sugol"
VALUE "FileDescription", "Books"
VALUE "FileVersion", "0.2.1"
VALUE "FileVersion", "0.2.2"
VALUE "InternalName", "Books"
VALUE "LegalCopyright", "al (al@sugol.org)"
VALUE "LegalCopyright", "al (alister@sugol.org)"
VALUE "OriginalFilename", "books.exe"
VALUE "ProductName", "Books"
VALUE "ProductVersion", "0.2.1"
VALUE "ProductVersion", "0.2.2"
END
END
BLOCK "VarFileInfo"

@ -0,0 +1,22 @@
#!/bin/bash
# Copyright (C) 2021 Alister Sanders
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
convert ../icons/org.sugol.books-16.png ../icons/org.sugol.books-32.png ../icons/org.sugol.books-256.png books.ico

@ -1,35 +0,0 @@
#pragma once
#include <optional>
#include <featuredetectorparams.hpp>
#include <fitnessmetrics.hpp>
#include <imagedata.hpp>
#include <worker.hpp>
namespace worker {
class FeatureDetector : public Worker<img::ImageData, img::ImageData> {
public:
const double CV_IMAGE_TARGET_DIMENSION = 400.0;
FeatureDetector(std::shared_ptr<InputQueue> input_queue,
std::shared_ptr<OutputQueue> output_queue,
const std::vector<ft::FitnessMetric>& fitness_metrics,
const std::vector<float>& fitness_metric_weights,
const FeatureDetectorParams& params);
void run(IWorkerPool* wp) override;
private:
cv::UMat apply_filters(const cv::UMat& mat);
void find_features(img::ImageData& image_data);
double resize_mat(const cv::UMat& src, cv::UMat& dest);
float calculate_fitness(const cv::Mat& mat, const util::Box& box);
std::optional<img::ImageData::Feature> best_candidate_box(std::vector<img::ImageData::Feature>& features);
private:
std::vector<ft::FitnessMetric> m_fitness_metrics;
std::vector<float> m_fitness_weights;
const FeatureDetectorParams& m_params;
};
}

@ -1,18 +0,0 @@
#pragma once
#include <cstdint>
namespace worker {
struct FeatureDetectorParams {
FeatureDetectorParams(uint8_t blur = 11,
uint8_t dilate = 4,
uint8_t threshold = 50)
: blur_kernel_size(blur),
dilate_kernel_size(dilate),
threshold(threshold) { }
uint8_t blur_kernel_size;
uint8_t dilate_kernel_size;
uint8_t threshold;
};
}

@ -1,80 +0,0 @@
#include <featuredetectorpool.hpp>
namespace worker {
FeatureDetectorPool::FeatureDetectorPool(size_t n_workers,
std::vector<ft::FitnessMetric> fitness_metrics,
std::vector<float> fitness_weights,
FeatureDetectorParams params)
: m_params(params) {
m_active_workers = n_workers;
m_stopped = false;
m_input_queue = std::make_shared<InputQueue>();
m_output_queue = std::make_shared<OutputQueue>();
for (size_t i = 0; i < n_workers; i++) {
m_workers.push_back(std::make_unique<FeatureDetector>(m_input_queue,
m_output_queue,
fitness_metrics,
fitness_weights,
m_params));
}
}
FeatureDetectorPool::~FeatureDetectorPool() {
join_all();
}
std::shared_ptr<FeatureDetectorPool::InputQueue> FeatureDetectorPool::input() {
return m_input_queue;
}
std::shared_ptr<FeatureDetectorPool::OutputQueue> FeatureDetectorPool::output() {
return m_output_queue;
}
void FeatureDetectorPool::set_input(std::shared_ptr<InputQueue> in) {
m_input_queue = in;
for (auto& worker : m_workers) {
worker->set_input_queue(m_input_queue);
}
}
void FeatureDetectorPool::set_output(std::shared_ptr<OutputQueue> out) {
m_output_queue = out;
}
void FeatureDetectorPool::run_workers() {
for (auto& worker : m_workers) {
worker->run(this);
}
}
void FeatureDetectorPool::join_all() {
for (auto& worker : m_workers) {
worker->join();
}
}
void FeatureDetectorPool::signal_done() {
std::lock_guard lock(m_mutex);
m_active_workers--;
if (m_active_workers == 0) {
m_output_queue->finish();
}
}
void FeatureDetectorPool::stop() {
m_input_queue->finish();
m_input_queue->clear();
m_output_queue->finish();
m_output_queue->clear();
m_stopped = true;
}
bool FeatureDetectorPool::stopped() {
return m_stopped;
}
}

@ -1,38 +0,0 @@
#pragma once
#include <featuredetector.hpp>
#include <featuredetectorparams.hpp>
#include <workerpool.hpp>
namespace worker {
class FeatureDetectorPool : public WorkerPool<FeatureDetector> {
public:
FeatureDetectorPool() { }
FeatureDetectorPool(size_t n_workers,
std::vector<ft::FitnessMetric> fitness_metrics,
std::vector<float> fitness_weights,
FeatureDetectorParams params = FeatureDetectorParams());
~FeatureDetectorPool();
std::shared_ptr<InputQueue> input() override;
std::shared_ptr<OutputQueue> output() override;
void set_input(std::shared_ptr<InputQueue> in) override;
void set_output(std::shared_ptr<OutputQueue> out) override;
void run_workers() override;
void join_all() override;
void signal_done() override;
void stop() override;
bool stopped() override;
private:
std::shared_ptr<InputQueue> m_input_queue;
std::shared_ptr<OutputQueue> m_output_queue;
mutable std::mutex m_mutex;
size_t m_active_workers;
std::vector<std::unique_ptr<FeatureDetector>> m_workers;
FeatureDetectorParams m_params;
bool m_stopped;
};
}

@ -1,13 +0,0 @@
#include "opencv2/imgproc.hpp"
#include <filter/bgr2grey.hpp>
namespace filter {
BGR2GreyFilter::~BGR2GreyFilter() {}
cv::Mat BGR2GreyFilter::apply(const cv::Mat &img) {
cv::Mat img_grey;
cv::cvtColor(img, img_grey, cv::COLOR_BGR2GRAY);
return img_grey;
}
}

@ -1,11 +0,0 @@
#pragma once
#include <filter/filter.hpp>
namespace filter {
class BGR2GreyFilter : public Filter {
public:
~BGR2GreyFilter();
cv::Mat apply(cv::Mat const& img) override;
};
}

@ -1,25 +0,0 @@
#include "opencv2/imgproc.hpp"
#include <filter/canny.hpp>
namespace filter {
CannyFilter::~CannyFilter() {
}
cv::Mat CannyFilter::apply(const cv::Mat& img) {
double low_thres, high_thres;
get_thresholds(img, low_thres, high_thres);
cv::Mat canny;
cv::Canny(img, canny, low_thres, high_thres);
return canny;
}
void CannyFilter::get_thresholds(const cv::Mat& img, double& low, double& high) {
cv::Mat dummy;
// Automagically determine threshold values for the canny filter. Do you know how this works? I sure don't.
high = cv::threshold(img, dummy, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
low = 0.5 * high;
}
}

@ -1,13 +0,0 @@
#pragma once
#include <filter/filter.hpp>
namespace filter {
class CannyFilter : public Filter {
public:
~CannyFilter();
cv::Mat apply(cv::Mat const& img) override;
private:
void get_thresholds(cv::Mat const& img, double& low, double& high);
};
}

@ -1,17 +0,0 @@
#include <filter/dilate.hpp>
namespace filter {
DilateFilter::DilateFilter(int kernel_size)
: m_kernelSize(kernel_size) { }
DilateFilter::~DilateFilter() {}
cv::Mat DilateFilter::apply(const cv::Mat &img) {
cv::Mat dilated;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(m_kernelSize, m_kernelSize));
cv::dilate(img, dilated, kernel);
return dilated;
}
}

@ -1,15 +0,0 @@
#pragma once
#include <filter/filter.hpp>
namespace filter {
class DilateFilter : public Filter {
public:
DilateFilter(int kernel_size = 8);
~DilateFilter();
cv::Mat apply(cv::Mat const& img) override;
private:
int m_kernelSize;
};
}

@ -1,11 +0,0 @@
#pragma once
#include <opencv2/imgproc.hpp>
namespace filter {
class Filter {
public:
virtual ~Filter() { };
virtual cv::Mat apply(cv::Mat const& img) = 0;
};
}

@ -1,34 +0,0 @@
#include "filter/canny.hpp"
#include "filter/medianblur.hpp"
#include <filter/filterchain.hpp>
#include <filter/filters_all.hpp>
namespace filter {
FilterChain::FilterChain() {
// Construct the default filter chain
// TODO: Allow for custom filter chains
m_filters.emplace_back(new BGR2GreyFilter);
m_filters.emplace_back(new MedianBlurFilter(11));
m_filters.emplace_back(new NormaliseFilter);
m_filters.emplace_back(new DilateFilter(4));
m_filters.emplace_back(new ThresholdFilter);
m_filters.emplace_back(new CannyFilter);
}
FilterChain::~FilterChain() {
for (auto filter : m_filters){
delete filter;
}
}
cv::Mat FilterChain::apply_filters(const cv::Mat& img) {
cv::Mat filter_result = img;
for (auto& filter : m_filters) {
filter_result = filter->apply(filter_result);
}
return filter_result;
}
}

@ -1,19 +0,0 @@
#pragma once
#include <opencv2/imgproc.hpp>
#include <vector>
#include <map>
#include <filter/filter.hpp>
namespace filter {
class FilterChain {
public:
FilterChain();
~FilterChain();
cv::Mat apply_filters(const cv::Mat& img);
private:
std::vector<Filter*> m_filters;
};
}

@ -1,8 +0,0 @@
#pragma once
#include <filter/bgr2grey.hpp>
#include <filter/medianblur.hpp>
#include <filter/normalise.hpp>
#include <filter/dilate.hpp>
#include <filter/threshold.hpp>
#include <filter/canny.hpp>

@ -1,16 +0,0 @@
#include "opencv2/imgproc.hpp"
#include <filter/medianblur.hpp>
namespace filter {
MedianBlurFilter::MedianBlurFilter(int kernel_size)
: m_kernel_size(kernel_size) { }
MedianBlurFilter::~MedianBlurFilter() {}
cv::Mat MedianBlurFilter::apply(const cv::Mat& img) {
cv::Mat blurred;
cv::medianBlur(img, blurred, m_kernel_size);
return blurred;
}
}

@ -1,15 +0,0 @@
#pragma once
#include <filter/filter.hpp>
namespace filter {
class MedianBlurFilter : public Filter {
public:
MedianBlurFilter(int kernel_size = 11);
~MedianBlurFilter();
cv::Mat apply(cv::Mat const& img) override;
private:
int m_kernel_size;
};
}

@ -1,13 +0,0 @@
#include "opencv2/core.hpp"
#include "opencv2/core/base.hpp"
#include <filter/normalise.hpp>
namespace filter {
NormaliseFilter::~NormaliseFilter() {}
cv::Mat NormaliseFilter::apply(const cv::Mat& img) {
cv::Mat normalised;
cv::normalize(img, normalised, 255, 0, cv::NORM_MINMAX);
return normalised;
}
}

@ -1,11 +0,0 @@
#pragma once
#include <filter/filter.hpp>
namespace filter {
class NormaliseFilter : public Filter {
public:
~NormaliseFilter();
cv::Mat apply(cv::Mat const& img) override;
};
}

@ -1,11 +0,0 @@
#include <filter/threshold.hpp>
namespace filter {
ThresholdFilter::~ThresholdFilter() {}
cv::Mat ThresholdFilter::apply(const cv::Mat &img) {
cv::Mat threshold_img;
cv::threshold(img, threshold_img, 60, 255, cv::THRESH_BINARY);
return threshold_img;
}
}

@ -1,11 +0,0 @@
#pragma once
#include <filter/filter.hpp>
namespace filter {
class ThresholdFilter : public Filter {
public:
~ThresholdFilter();
cv::Mat apply(cv::Mat const& img) override;
};
}

@ -1,16 +0,0 @@
#pragma once
#include <functional>
#include <opencv2/imgproc.hpp>
#include <util/box.hpp>
#include <util/box.hpp>
namespace ft {
using FitnessMetric = std::function<double(const cv::Mat&, const util::Box&)>;
FitnessMetric aspect_ratio(double aspect);
FitnessMetric distance_to(util::Point<double> target);
FitnessMetric relative_area(double ideal_area);
}

@ -1,59 +0,0 @@
#include <iostream>
#include <filesystem>
#include <imageexporterpool.hpp>
#include <imageexporter.hpp>
namespace fs { using path = std::filesystem::path; }
namespace worker {
ImageExporter::ImageExporter(std::shared_ptr<InputQueue> input_queue,
std::shared_ptr<OutputQueue> output_queue) {
set_input_queue(input_queue);
set_output_queue(output_queue);
}
void ImageExporter::run(IWorkerPool* wp) {
auto work_fn = [=] {
// TODO: ImageExporterPool
auto worker_pool = static_cast<ImageExporterPool*>(wp);
std::shared_ptr<std::pair<std::shared_ptr<img::ImageData>, ExportParameters>> image = nullptr;
while ((image = m_input_queue->pop()) != nullptr) {
auto image_data = image->first;
auto params = image->second;
util::Box crop = image_data->candidate().second;
util::Box bounds;
image_data->load(image_data->filename());
bounds.top_left() = { 0, 0 };
bounds.bottom_right() = { image_data->pixbuf()->get_width(), image_data->pixbuf()->get_height() };
crop.expand(params.margin, bounds);
cv::Mat output_image;
if (params.do_crop && crop.area() > 0) {
try {
output_image = image_data->mat()(crop);
} catch (cv::Exception& e) {
std::cout << crop.top_left().x << " " << crop.top_left().y << " "
<< crop.bottom_right().x << " " << crop.bottom_right().y << std::endl;
std::cout << image_data->mat().size().width << " " << image_data->mat().size().height << std::endl;
}
} else {
output_image = image_data->mat();
}
fs::path output_path = fs::path(params.export_directory) / fs::path(params.output_file);
cv::cvtColor(output_image, output_image, cv::COLOR_RGB2BGR);
cv::imwrite(output_path.string(), output_image);
image_data->unload();
m_output_queue->push(nullptr);
}
worker_pool->signal_done();
};
m_thread = std::thread(work_fn);
}
}

@ -1,21 +0,0 @@
#pragma once
#include <imagedata.hpp>
#include <worker.hpp>
namespace worker {
struct ExportParameters {
std::string export_directory;
size_t margin;
std::string output_file;
bool do_crop;
};
class ImageExporter : public Worker<std::pair<std::shared_ptr<img::ImageData>, ExportParameters>, void*> {
public:
ImageExporter(std::shared_ptr<InputQueue> input_queue,
std::shared_ptr<OutputQueue> output_queue);
void run(IWorkerPool* wp) override;
};
}

@ -1,68 +0,0 @@
#include <imageexporterpool.hpp>
namespace worker {
ImageExporterPool::ImageExporterPool(size_t n_workers) {
m_active_workers = n_workers;
m_stopped = false;
m_input_queue = std::make_shared<InputQueue>();
m_output_queue = std::make_shared<OutputQueue>();
for (size_t i = 0; i < n_workers; i++) {
m_workers.push_back(std::make_unique<ImageExporter>(m_input_queue, m_output_queue));
}
}
ImageExporterPool::~ImageExporterPool() {
join_all();
}
std::shared_ptr<ImageExporterPool::InputQueue> ImageExporterPool::input() {
return m_input_queue;
}
std::shared_ptr<ImageExporterPool::OutputQueue> ImageExporterPool::output() {
return m_output_queue;
}
void ImageExporterPool::set_input(std::shared_ptr<InputQueue> in) {
m_input_queue = in;
}
void ImageExporterPool::set_output(std::shared_ptr<OutputQueue> out) {
m_output_queue = out;
}
void ImageExporterPool::run_workers() {
for (auto& worker : m_workers) {
worker->run(this);
}
}
void ImageExporterPool::join_all() {
for (auto& worker : m_workers) {
worker->join();
}
}
void ImageExporterPool::signal_done() {
std::lock_guard lock(m_mutex);
m_active_workers--;
if (m_active_workers == 0) {
m_output_queue->finish();
}
}
void ImageExporterPool::stop() {
m_input_queue->finish();
m_input_queue->clear();
m_output_queue->finish();
m_output_queue->clear();
m_stopped = true;
}
bool ImageExporterPool::stopped() {
return m_stopped;
}
}

@ -1,34 +0,0 @@
#pragma once
#include <imageexporter.hpp>
#include <workerpool.hpp>
namespace worker {
class ImageExporterPool : public WorkerPool<ImageExporter> {
public:
ImageExporterPool() { };
ImageExporterPool(size_t n_workers);
~ImageExporterPool();
std::shared_ptr<InputQueue> input() override;
std::shared_ptr<OutputQueue> output() override;
void set_input(std::shared_ptr<InputQueue> in) override;
void set_output(std::shared_ptr<OutputQueue> out) override;
void run_workers() override;
void join_all() override;
void signal_done() override;
void stop() override;
bool stopped() override;
private:
std::shared_ptr<InputQueue> m_input_queue;
std::shared_ptr<OutputQueue> m_output_queue;
mutable std::mutex m_mutex;
size_t m_active_workers;
std::vector<std::unique_ptr<ImageExporter>> m_workers;
bool m_stopped;
};
}

@ -1,27 +0,0 @@
#include <imageloaderpool.hpp>
#include <imageloader.hpp>
namespace worker {
ImageLoader::ImageLoader(std::shared_ptr<InputQueue> input_queue,
std::shared_ptr<OutputQueue> output_queue) {
set_input_queue(input_queue);
set_output_queue(output_queue);
}
void ImageLoader::run(IWorkerPool* wp) {
auto work_fn = [=] {
ImageLoaderPool* worker_pool = static_cast<ImageLoaderPool*>(wp);
std::shared_ptr<std::string> filename = nullptr;
while ((filename = m_input_queue->pop()) != nullptr) {
std::shared_ptr<img::ImageData> image = std::make_shared<img::ImageData>(*filename);
m_output_queue->push(std::move(image));
}
// This worker is finished
worker_pool->signal_done();
};
m_thread = std::thread(work_fn);
}
}

@ -1,14 +0,0 @@
#pragma once
#include <imagedata.hpp>
#include <worker.hpp>
namespace worker {
class ImageLoader : public Worker<std::string, img::ImageData> {
public:
ImageLoader(std::shared_ptr<InputQueue> input_queue,
std::shared_ptr<OutputQueue> output_queue);
void run(IWorkerPool* wp) override;
};
}

@ -1,71 +0,0 @@
#include <imageloaderpool.hpp>
namespace worker {
ImageLoaderPool::ImageLoaderPool(size_t n_workers) {
m_active_workers = n_workers;
m_stopped = false;
m_input_queue = std::make_shared<InputQueue>();
m_output_queue = std::make_shared<OutputQueue>();
// Create the workers
for (size_t i = 0; i < n_workers; i++) {
m_workers.push_back(std::make_unique<ImageLoader>(m_input_queue, m_output_queue));
}
}
ImageLoaderPool::~ImageLoaderPool() {
join_all();
}
std::shared_ptr<ImageLoaderPool::InputQueue> ImageLoaderPool::input() {
return m_input_queue;
}
std::shared_ptr<ImageLoaderPool::OutputQueue> ImageLoaderPool::output() {
return m_output_queue;
}
void ImageLoaderPool::set_input(std::shared_ptr<InputQueue> in) {
m_input_queue = in;
}
void ImageLoaderPool::set_output(std::shared_ptr<OutputQueue> out) {
m_output_queue = out;
}
void ImageLoaderPool::run_workers() {
for (auto& worker : m_workers) {
worker->run(this);
}
}
void ImageLoaderPool::join_all() {
for (auto& worker : m_workers) {
worker->join();
}
}
void ImageLoaderPool::signal_done() {
std::lock_guard lock(m_mutex);
m_active_workers--;
// We're done producing, so signal the consumers to stop consuming once
// the output queue runs out
if (m_active_workers == 0) {
m_output_queue->finish();
}
}
void ImageLoaderPool::stop() {
m_input_queue->finish();
m_input_queue->clear();
m_output_queue->finish();
m_output_queue->clear();
m_stopped = true;
}
bool ImageLoaderPool::stopped() {
return m_stopped;