add mask
This commit is contained in:
parent
4447cd67d1
commit
5b32c517c1
70
README.md
70
README.md
|
@ -12,6 +12,7 @@ The subject is available here: [Test Algo](./test_algo.pdf)
|
||||||
- [ ] wrap stb_rect_pack?
|
- [ ] wrap stb_rect_pack?
|
||||||
- [X] change bbox api to origin + size
|
- [X] change bbox api to origin + size
|
||||||
- [X] dummy A2
|
- [X] dummy A2
|
||||||
|
- [X] package
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -19,13 +20,71 @@ The subject is available here: [Test Algo](./test_algo.pdf)
|
||||||
|
|
||||||
### Question 1
|
### Question 1
|
||||||
|
|
||||||
|
> As a preprocessing step of a second algorithm A2, we would like to combine all
|
||||||
|
> the regions corresponding to the bounding boxes into a new image FRegions of
|
||||||
|
> dimension D × D, where D given by A2 and D < min(M, N )
|
||||||
|
|
||||||
This is a packing problem, as described on [Wikipedia][1].
|
This is a packing problem, as described on [Wikipedia][1].
|
||||||
Since this is NP-hard, we know the "exact" solution might be unreachable but we
|
Since this is NP-hard, we know the "exact" solution might be unreachable but we
|
||||||
could find a solution that is good enough for our needs.
|
could find a solution that is good enough for our needs.
|
||||||
|
|
||||||
|
I looked up some solutions online and found a great article by David Colson:
|
||||||
|
"[Exploring rectangle packing algorithms][2]". It gives a lot a references and
|
||||||
|
compares different algorithms.
|
||||||
|
|
||||||
|
I decided at first to implement his naive "row packer" to quickly have an
|
||||||
|
implementation and try it out.
|
||||||
|
|
||||||
### Question 2
|
### Question 2
|
||||||
|
|
||||||
|
> A2 then takes as input F Regions and outputs new bounding boxes B′. We would
|
||||||
|
> like now to compute the location of each of these new bounding boxes in F
|
||||||
|
> reference
|
||||||
|
|
||||||
|
To map the new bounding boxes into the original frame F, we need to have a
|
||||||
|
mapping between the bounding boxes of F and the ones we packed into FRegions.
|
||||||
|
During the packing, I saved the bounding boxes position in FRegions, so the
|
||||||
|
mapping was straightforward:
|
||||||
|
|
||||||
|
1. detect in which bounding box in FRegions the new bounding box is contained
|
||||||
|
2. apply the transformation from FRegions to F
|
||||||
|
|
||||||
|
Because we look into every bounding box to find the enclosing one, we have a
|
||||||
|
complexity of N^2. Depending of the numbers of boxes, it could be problematic.
|
||||||
|
We can make it faster by using a spatially sorted structure like a quad-tree,
|
||||||
|
but it seems a bit overkill in this case.
|
||||||
|
|
||||||
|
If we didn't have knowledge of the bounding boxes position in FRegions, we could
|
||||||
|
recompute them by looking at pixel similarity between F and FRegions but it
|
||||||
|
would be costly, and a bit wasteful since we already computed the bounding
|
||||||
|
boxes.
|
||||||
|
|
||||||
### Question 3
|
### Question 3
|
||||||
|
|
||||||
|
> We would like now to be able to provide to the algorithm A2 either the
|
||||||
|
> region-based image or the initial image without transformation, which
|
||||||
|
> modifications to your code architecture do you suggest in order to handle
|
||||||
|
> this?
|
||||||
|
|
||||||
|
To support either a packed image (the region-based one) or a sparse image (the
|
||||||
|
initial image limited to the bounding boxes), the easiest solution is to add
|
||||||
|
support for a binary mask. In the same way A2 is suppose to ignore zeros valud
|
||||||
|
in the region-based frame, it could be modified to ignore areas in an image
|
||||||
|
where the mask is set to zero.
|
||||||
|
|
||||||
|
## GUI
|
||||||
|
|
||||||
|
In order to easily debug and better visualize the problem, I chose to implement
|
||||||
|
a minimal GUI using [raylib][3].
|
||||||
|
|
||||||
|
You can add, move, and resize boxes. Processing steps are triggered with
|
||||||
|
buttons.
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
A commandline sample is also available, in case the raylib library cannot be
|
||||||
|
built, or if we need to benchmark performance.
|
||||||
|
|
||||||
## PNG support
|
## PNG support
|
||||||
|
|
||||||
I chose to support PNG files through the stb files:
|
I chose to support PNG files through the stb files:
|
||||||
|
@ -33,11 +92,6 @@ I chose to support PNG files through the stb files:
|
||||||
I could have implemented basic image support with the PNM format but I think it
|
I could have implemented basic image support with the PNM format but I think it
|
||||||
is nicer to support common image formats with a simple library.
|
is nicer to support common image formats with a simple library.
|
||||||
|
|
||||||
stb also implements a 2D rectangle packer:
|
|
||||||
[stb_rect_pack.h](https://github.com/nothings/stb/blob/master/stb_rect_pack.h)
|
|
||||||
I added this file in the project in order to compare my implementation to
|
|
||||||
another one.
|
|
||||||
|
|
||||||
## 3rd party libraries
|
## 3rd party libraries
|
||||||
|
|
||||||
I use 2 external libraries for better visualization:
|
I use 2 external libraries for better visualization:
|
||||||
|
@ -51,8 +105,6 @@ To avoid name collision, I created my own namespace `freling`.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [Exploring rectangle packing algorithms](https://www.david-colson.com/2020/03/10/exploring-rect-packing.html)
|
|
||||||
- [A Skyline-Based Heuristic for the 2D Rectangular Strip Packing Problem](https://www.researchgate.net/publication/221049934_A_Skyline-Based_Heuristic_for_the_2D_Rectangular_Strip_Packing_Problem)
|
|
||||||
- [New Improvements in Optimal Rectangle Packing](https://www.ijcai.org/Proceedings/09/Papers/092.pdf)
|
|
||||||
|
|
||||||
[1]: https://en.wikipedia.org/wiki/Packing_problems#Packing_of_rectangles
|
[1]: https://en.wikipedia.org/wiki/Packing_problems#Packing_of_rectangles
|
||||||
|
[2]: https://www.david-colson.com/2020/03/10/exploring-rect-packing.html
|
||||||
|
[3]: https://github.com/raysan5/raylib
|
||||||
|
|
2
justfile
2
justfile
|
@ -43,5 +43,5 @@ massif: build-cli
|
||||||
callgrind: build-cli
|
callgrind: build-cli
|
||||||
valgrind --tool=callgrind {{exe_cli}} {{params}}
|
valgrind --tool=callgrind {{exe_cli}} {{params}}
|
||||||
|
|
||||||
archive: generate-build
|
archive: #generate-build
|
||||||
git archive --add-file={{build_sh}} --output={{name}}.zip --prefix={{name}}/ HEAD
|
git archive --add-file={{build_sh}} --output={{name}}.zip --prefix={{name}}/ HEAD
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
#include "a2.h"
|
#include "a2.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
namespace freling {
|
namespace freling {
|
||||||
|
|
||||||
|
std::vector<BoundingBox> A2(const Frame& frame, const Mask& mask) {
|
||||||
|
assert((bool)"Not implemented\n" && false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<BoundingBox> A2(const std::vector<BoundingBox>& regions) {
|
std::vector<BoundingBox> A2(const std::vector<BoundingBox>& regions) {
|
||||||
std::vector<BoundingBox> updated_boxes(regions.size());
|
std::vector<BoundingBox> updated_boxes(regions.size());
|
||||||
for (int i = 0, size = regions.size(); i < size; ++i) {
|
for (int i = 0, size = regions.size(); i < size; ++i) {
|
||||||
|
|
6
src/a2.h
6
src/a2.h
|
@ -3,10 +3,14 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "bounding_box.h"
|
#include "bounding_box.h"
|
||||||
|
#include "frame.h"
|
||||||
|
#include "mask.h"
|
||||||
|
|
||||||
namespace freling {
|
namespace freling {
|
||||||
|
|
||||||
// The normal signature for this function should be:
|
std::vector<BoundingBox> A2(const Frame& frame, const Mask& mask);
|
||||||
|
|
||||||
|
// The normal signature for this function is the one above:
|
||||||
// std::vector<BoundingBox> A2(const Frame& regions);
|
// std::vector<BoundingBox> A2(const Frame& regions);
|
||||||
// However, since we are mocking this algorithm we just need to fulfill this
|
// However, since we are mocking this algorithm we just need to fulfill this
|
||||||
// constraint: "We assume for the sake of simplicity, that no bounding boxe
|
// constraint: "We assume for the sake of simplicity, that no bounding boxe
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace freling {
|
||||||
|
|
||||||
Frame::Frame(uint32_t width, uint32_t height) : width(width), height(height) {
|
Frame::Frame(uint32_t width, uint32_t height) : width(width), height(height) {
|
||||||
data = new Pixel[width * height];
|
data = new Pixel[width * height];
|
||||||
}
|
}
|
||||||
|
@ -33,3 +35,5 @@ Frame::~Frame() {
|
||||||
void Frame::fill(uint8_t p) {
|
void Frame::fill(uint8_t p) {
|
||||||
std::memset(data, p, width * height * sizeof(Pixel));
|
std::memset(data, p, width * height * sizeof(Pixel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace freling
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace freling {
|
||||||
|
|
||||||
struct Pixel {
|
struct Pixel {
|
||||||
uint8_t r;
|
uint8_t r;
|
||||||
uint8_t g;
|
uint8_t g;
|
||||||
|
@ -21,3 +23,5 @@ struct Frame {
|
||||||
|
|
||||||
void fill(uint8_t p);
|
void fill(uint8_t p);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace freling
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "a2.h"
|
||||||
#include "bounding_box.h"
|
#include "bounding_box.h"
|
||||||
|
#include "mapping.h"
|
||||||
|
#include "mask.h"
|
||||||
#include "pack.h"
|
#include "pack.h"
|
||||||
#include "png.h"
|
#include "png.h"
|
||||||
|
|
||||||
|
@ -25,7 +28,7 @@ int main(int argc, const char* argv[]) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::optional<Frame> in_frame = load_png(input);
|
const std::optional<freling::Frame> in_frame = freling::load_png(input);
|
||||||
if (!in_frame) {
|
if (!in_frame) {
|
||||||
std::cerr << "Cannot load file " << input << "\n";
|
std::cerr << "Cannot load file " << input << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -42,9 +45,24 @@ int main(int argc, const char* argv[]) {
|
||||||
bboxes.push_back(freling::BoundingBox({x, y, w, h}));
|
bboxes.push_back(freling::BoundingBox({x, y, w, h}));
|
||||||
i += 4;
|
i += 4;
|
||||||
}
|
}
|
||||||
|
freling::Mask in_mask(in_frame->width, in_frame->height);
|
||||||
|
|
||||||
std::vector<freling::BoundingBox> packed_bboxes;
|
std::vector<freling::BoundingBox> packed_bboxes;
|
||||||
std::optional<Frame> regions = pack(*in_frame, bboxes, packed_bboxes);
|
std::optional<freling::Frame> regions =
|
||||||
|
pack(*in_frame, bboxes, packed_bboxes);
|
||||||
|
|
||||||
|
in_mask.fill(false);
|
||||||
|
for (const auto& b : bboxes) {
|
||||||
|
for (int j = b.y; j < b.y + b.height; ++j) {
|
||||||
|
for (int i = b.x; i < b.x + b.width; ++i) {
|
||||||
|
in_mask.set(i, j, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<freling::BoundingBox> a2_bboxes = freling::A2(packed_bboxes);
|
||||||
|
std::vector<freling::BoundingBox> a2_bboxes_origin =
|
||||||
|
freling::map_to_origin(bboxes, packed_bboxes, a2_bboxes);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "bounding_box.h"
|
#include "bounding_box.h"
|
||||||
#include "frame.h"
|
#include "frame.h"
|
||||||
#include "mapping.h"
|
#include "mapping.h"
|
||||||
|
#include "mask.h"
|
||||||
#include "pack.h"
|
#include "pack.h"
|
||||||
#include "png.h"
|
#include "png.h"
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ void draw_region(const freling::BoundingBox& box,
|
||||||
DrawRectangleRec(rect, ColorAlpha(color, 0.8));
|
DrawRectangleRec(rect, ColorAlpha(color, 0.8));
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image_from_frame(const Frame& frame) {
|
Image image_from_frame(const freling::Frame& frame) {
|
||||||
Image image = {0};
|
Image image = {0};
|
||||||
|
|
||||||
const int format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
|
const int format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
|
||||||
|
@ -84,7 +85,7 @@ int main(int argc, const char* argv[]) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::optional<Frame> in_frame = load_png(input);
|
const std::optional<freling::Frame> in_frame = freling::load_png(input);
|
||||||
if (!in_frame) {
|
if (!in_frame) {
|
||||||
std::cerr << "Cannot load file " << input << "\n";
|
std::cerr << "Cannot load file " << input << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -106,6 +107,7 @@ int main(int argc, const char* argv[]) {
|
||||||
std::vector<freling::BoundingBox> packed_bboxes;
|
std::vector<freling::BoundingBox> packed_bboxes;
|
||||||
std::vector<freling::BoundingBox> a2_bboxes;
|
std::vector<freling::BoundingBox> a2_bboxes;
|
||||||
std::vector<freling::BoundingBox> a2_bboxes_origin;
|
std::vector<freling::BoundingBox> a2_bboxes_origin;
|
||||||
|
freling::Mask in_mask(in_frame->width, in_frame->height);
|
||||||
|
|
||||||
Vector2 win_size = {800, 450};
|
Vector2 win_size = {800, 450};
|
||||||
const int padding = 5;
|
const int padding = 5;
|
||||||
|
@ -205,9 +207,19 @@ int main(int argc, const char* argv[]) {
|
||||||
b_size.y},
|
b_size.y},
|
||||||
"Pack Rects")) {
|
"Pack Rects")) {
|
||||||
a2_processed = false;
|
a2_processed = false;
|
||||||
std::optional<Frame> f_packed =
|
std::optional<freling::Frame> f_packed =
|
||||||
freling::pack(*in_frame, bboxes, packed_bboxes);
|
freling::pack(*in_frame, bboxes, packed_bboxes);
|
||||||
|
|
||||||
|
// Update mask
|
||||||
|
in_mask.fill(false);
|
||||||
|
for (const auto& b : bboxes) {
|
||||||
|
for (int j = b.y; j < b.y + b.height; ++j) {
|
||||||
|
for (int i = b.x; i < b.x + b.width; ++i) {
|
||||||
|
in_mask.set(i, j, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (packed) {
|
if (packed) {
|
||||||
UnloadTexture(pack_texture);
|
UnloadTexture(pack_texture);
|
||||||
UnloadImage(pack_img);
|
UnloadImage(pack_img);
|
||||||
|
|
34
src/mask.cpp
Normal file
34
src/mask.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "mask.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace freling {
|
||||||
|
|
||||||
|
Mask::Mask(uint32_t width, uint32_t height) : width(width), height(height) {
|
||||||
|
data.resize(width * height, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mask::fill(bool value) {
|
||||||
|
for (int i = 0, size = data.size(); i < size; ++i) {
|
||||||
|
data[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mask::set(int x, int y, bool value) {
|
||||||
|
assert(x < width);
|
||||||
|
assert(y < height);
|
||||||
|
|
||||||
|
const int i = x + y * width;
|
||||||
|
data[i] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mask::get(int x, int y) {
|
||||||
|
assert(x < width);
|
||||||
|
assert(y < height);
|
||||||
|
|
||||||
|
const int i = x + y * width;
|
||||||
|
return data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace freling
|
20
src/mask.h
Normal file
20
src/mask.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace freling {
|
||||||
|
|
||||||
|
struct Mask {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
std::vector<bool> data; // std::vector is specialized for bool
|
||||||
|
|
||||||
|
Mask(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
void fill(bool b);
|
||||||
|
void set(int x, int y, bool b);
|
||||||
|
bool get(int x, int y);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace freling
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "stb_image.h"
|
#include "stb_image.h"
|
||||||
|
|
||||||
|
namespace freling {
|
||||||
|
|
||||||
std::optional<Frame> load_png(const std::filesystem::path& in) {
|
std::optional<Frame> load_png(const std::filesystem::path& in) {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
int y = 0;
|
int y = 0;
|
||||||
|
@ -22,3 +24,5 @@ std::optional<Frame> load_png(const std::filesystem::path& in) {
|
||||||
stbi_image_free(data);
|
stbi_image_free(data);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace freling
|
||||||
|
|
Loading…
Reference in a new issue