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?
|
||||
- [X] change bbox api to origin + size
|
||||
- [X] dummy A2
|
||||
- [X] package
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -19,13 +20,71 @@ The subject is available here: [Test Algo](./test_algo.pdf)
|
|||
|
||||
### 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].
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
> 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
|
||||
|
||||
> 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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
I use 2 external libraries for better visualization:
|
||||
|
@ -51,8 +105,6 @@ To avoid name collision, I created my own namespace `freling`.
|
|||
|
||||
## 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
|
||||
[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
|
||||
valgrind --tool=callgrind {{exe_cli}} {{params}}
|
||||
|
||||
archive: generate-build
|
||||
archive: #generate-build
|
||||
git archive --add-file={{build_sh}} --output={{name}}.zip --prefix={{name}}/ HEAD
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
#include "a2.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
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> updated_boxes(regions.size());
|
||||
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 "bounding_box.h"
|
||||
#include "frame.h"
|
||||
#include "mask.h"
|
||||
|
||||
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);
|
||||
// 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
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <cstring>
|
||||
|
||||
namespace freling {
|
||||
|
||||
Frame::Frame(uint32_t width, uint32_t height) : width(width), height(height) {
|
||||
data = new Pixel[width * height];
|
||||
}
|
||||
|
@ -33,3 +35,5 @@ Frame::~Frame() {
|
|||
void Frame::fill(uint8_t p) {
|
||||
std::memset(data, p, width * height * sizeof(Pixel));
|
||||
}
|
||||
|
||||
} // namespace freling
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
namespace freling {
|
||||
|
||||
struct Pixel {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
|
@ -21,3 +23,5 @@ struct Frame {
|
|||
|
||||
void fill(uint8_t p);
|
||||
};
|
||||
|
||||
} // namespace freling
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "a2.h"
|
||||
#include "bounding_box.h"
|
||||
#include "mapping.h"
|
||||
#include "mask.h"
|
||||
#include "pack.h"
|
||||
#include "png.h"
|
||||
|
||||
|
@ -25,7 +28,7 @@ int main(int argc, const char* argv[]) {
|
|||
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) {
|
||||
std::cerr << "Cannot load file " << input << "\n";
|
||||
return 1;
|
||||
|
@ -42,9 +45,24 @@ int main(int argc, const char* argv[]) {
|
|||
bboxes.push_back(freling::BoundingBox({x, y, w, h}));
|
||||
i += 4;
|
||||
}
|
||||
freling::Mask in_mask(in_frame->width, in_frame->height);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "bounding_box.h"
|
||||
#include "frame.h"
|
||||
#include "mapping.h"
|
||||
#include "mask.h"
|
||||
#include "pack.h"
|
||||
#include "png.h"
|
||||
|
||||
|
@ -52,7 +53,7 @@ void draw_region(const freling::BoundingBox& box,
|
|||
DrawRectangleRec(rect, ColorAlpha(color, 0.8));
|
||||
}
|
||||
|
||||
Image image_from_frame(const Frame& frame) {
|
||||
Image image_from_frame(const freling::Frame& frame) {
|
||||
Image image = {0};
|
||||
|
||||
const int format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
|
||||
|
@ -84,7 +85,7 @@ int main(int argc, const char* argv[]) {
|
|||
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) {
|
||||
std::cerr << "Cannot load file " << input << "\n";
|
||||
return 1;
|
||||
|
@ -106,6 +107,7 @@ int main(int argc, const char* argv[]) {
|
|||
std::vector<freling::BoundingBox> packed_bboxes;
|
||||
std::vector<freling::BoundingBox> a2_bboxes;
|
||||
std::vector<freling::BoundingBox> a2_bboxes_origin;
|
||||
freling::Mask in_mask(in_frame->width, in_frame->height);
|
||||
|
||||
Vector2 win_size = {800, 450};
|
||||
const int padding = 5;
|
||||
|
@ -205,9 +207,19 @@ int main(int argc, const char* argv[]) {
|
|||
b_size.y},
|
||||
"Pack Rects")) {
|
||||
a2_processed = false;
|
||||
std::optional<Frame> f_packed =
|
||||
std::optional<freling::Frame> f_packed =
|
||||
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) {
|
||||
UnloadTexture(pack_texture);
|
||||
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"
|
||||
|
||||
namespace freling {
|
||||
|
||||
std::optional<Frame> load_png(const std::filesystem::path& in) {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
@ -22,3 +24,5 @@ std::optional<Frame> load_png(const std::filesystem::path& in) {
|
|||
stbi_image_free(data);
|
||||
return f;
|
||||
}
|
||||
|
||||
} // namespace freling
|
||||
|
|
Loading…
Reference in a new issue