From e206b3f4a8d663db12cb4a57d6273ade4b779ae1 Mon Sep 17 00:00:00 2001 From: Joshua Moerman Date: Mon, 25 Aug 2014 22:09:35 +0200 Subject: [PATCH] Adds a mozaic datatype. Adds slack to mozaics. Gets rid of libav logging. --- lib/fingerprint_traits.hpp | 2 ++ lib/image_database.hpp | 37 ++++++++--------------- lib/mozaic.hpp | 51 +++++++++++++++++++++++++++++++ src/main.cpp | 61 +++++++++++++++++++++----------------- src/needle.cpp | 47 ----------------------------- 5 files changed, 99 insertions(+), 99 deletions(-) create mode 100644 lib/mozaic.hpp delete mode 100644 src/needle.cpp diff --git a/lib/fingerprint_traits.hpp b/lib/fingerprint_traits.hpp index 10e3076..88a6d05 100644 --- a/lib/fingerprint_traits.hpp +++ b/lib/fingerprint_traits.hpp @@ -13,4 +13,6 @@ struct fingerprint_traits { static pre_fingerprint pre_calculate(av::frame const & frame) { return fingerprint::pre_calculate(frame); } static fingerprint calculate(av::frame const & frame) { return fingerprint::calculate(frame); } static auto distance(fingerprint const & x, pre_fingerprint const & y) { return x.distance_to(y); } + + using distance_type = decltype(distance(std::declval(), std::declval())); }; diff --git a/lib/image_database.hpp b/lib/image_database.hpp index 75c37a2..bbdeb63 100644 --- a/lib/image_database.hpp +++ b/lib/image_database.hpp @@ -13,6 +13,10 @@ template > ret; + ret.reserve(size()); - Fingerprint fingerprint(index i) const { - return fingerprints.at(i); - } - - auto size() const { - return fingerprints.size(); - } - - index nearest_image(av::frame const & image) const { - const auto && pre_fingerprint = Traits::pre_calculate(image); - - index best_index = 0; - auto && best_distance = Traits::distance(fingerprints[0], pre_fingerprint); - - for(index i = 1; i < fingerprints.size(); ++i){ - const auto && distance = Traits::distance(fingerprints[i], pre_fingerprint); - if(distance < best_distance) { - best_distance = distance; - best_index = i; - } + auto const pre_fingerprint = Traits::pre_calculate(image); + for(auto&& fingerprint : fingerprints){ + ret.emplace_back(Traits::distance(fingerprint, pre_fingerprint), ret.size()); } - - return best_index; + return ret; } private: diff --git a/lib/mozaic.hpp b/lib/mozaic.hpp new file mode 100644 index 0000000..da877fc --- /dev/null +++ b/lib/mozaic.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +template +struct Mozaic { + Mozaic(int h_tiles_, int v_tiles_) + : h_tiles(h_tiles_) + , v_tiles(v_tiles_) + , tiles(v_tiles, std::vector(h_tiles)) + {} + + decltype(auto) operator[](size_t c) { return tiles[c]; } + decltype(auto) operator[](size_t c) const { return tiles[c]; } + + // aka width & height + int h_tiles = 0, v_tiles = 0; + + // tiles[row][column] + std::vector> tiles; +}; + +template +auto mozaic_fmap(Mozaic const & mozaic, F&& f){ + using return_type = decltype(f(mozaic[0][0])); + Mozaic ret(mozaic.h_tiles, mozaic.v_tiles); + + for(auto r = 0; r < mozaic.v_tiles; ++r){ + for(auto c = 0; c < mozaic.h_tiles; ++c){ + ret[r][c] = f(mozaic[r][c]); + } + } + + return ret; +} + +//! Currently takes one of the (slack+1) best fitting images +template +auto optimize(Mozaic> distances, int slack){ + std::random_device rd; + std::uniform_int_distribution dist(0, slack); + auto rand = [&]{ return dist(rd); }; + + return mozaic_fmap(distances, [&](auto d){ + std::nth_element(begin(d), begin(d) + slack, end(d)); + return (begin(d) + rand())->second; + }); +} diff --git a/src/main.cpp b/src/main.cpp index 11c58d1..8de89a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,7 @@ namespace fs = boost::filesystem; namespace ar = boost::archive; using Database = image_database; -using Mozaic = map, string>; +using Mozaiq = Mozaic; static Database read_database(string const & database_directory){ Database db; @@ -56,36 +57,36 @@ static Database read_database(string const & database_directory){ archive >> db; } - cout << colors::green("read database: ") << db.size() << endl; return db; } -static Mozaic create_mozaic(Database const & db, string const & filename, int h_tiles, int v_tiles){ - Mozaic mozaic; +static auto calculate_all_distances(Database const & db, string const & filename, int h_tiles, int v_tiles){ + using return_type = decltype(db.distances_for_image(std::declval())); + Mozaic mozaic(h_tiles, v_tiles); apply_to_tiles(filename, h_tiles, v_tiles, [&](int c, int r, av::frame const & frame){ - auto const index = db.nearest_image(frame); - cout << colors::red("tile ") << c << ", " << r << ": " << db.filename(index) << endl; - mozaic[make_pair(c, r)] = db.filename(index); + mozaic[r][c] = db.distances_for_image(frame); }); return mozaic; } -static void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int v_tiles){ +static void save_mozaic(Mozaiq const & mozaic, string filename){ auto const pix_fmt = AV_PIX_FMT_YUVJ444P; auto const codec_id= AV_CODEC_ID_MJPEG; // Open all files we need map frames; - for(auto&& x : mozaic){ - // only open a file once - if(frames.count(x.second) > 0) continue; - frames.emplace(x.second, crop_to_square(open_image(x.second))); + for(auto&& y : mozaic.tiles){ + for(auto&& x : y){ + // only open a file once + if(frames.count(x) > 0) continue; + frames.emplace(x, crop_to_square(open_image(x))); + } } - auto const total_width = h_tiles * tile_width; - auto const total_height = v_tiles * tile_height; + auto const total_width = mozaic.h_tiles * tile_width; + auto const total_height = mozaic.v_tiles * tile_height; // Create output frame std::vector> data(make_u(avpicture_get_size(pix_fmt, total_width, total_height)), 0); @@ -97,13 +98,13 @@ static void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int // For each tile: get the part, copy input to it av::frame frame_part = av::frame_clone(frame); - for(int r = 0; r < v_tiles; ++r) { - for(int c = 0; c < h_tiles; ++c){ + for(int r = 0; r < mozaic.v_tiles; ++r) { + for(int c = 0; c < mozaic.h_tiles; ++c){ av_picture_crop(reinterpret_cast(frame_part.get()), reinterpret_cast(frame.get()), av::get_format(frame), r * tile_height, c * tile_width); frame_part->width = tile_width; frame_part->height = tile_height; - auto&& input = frames.at(mozaic.at(make_pair(c, r))); + auto&& input = frames.at(mozaic[r][c]); auto sws_context = sws_getContext(input->width, input->height, av::get_format(input), frame_part->width, frame_part->height, av::get_format(frame_part), 0, nullptr, nullptr, nullptr); if(!sws_context) throw std::runtime_error("boem sws context"); sws_scale (sws_context, {input->data}, {input->linesize}, 0, input->height, {frame_part->data}, {frame_part->linesize}); @@ -131,7 +132,6 @@ static void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int std::vector buffer(make_u(buffer_size), 0); auto const output_size = avcodec_encode_video(codec_ctx.get(), buffer.data(), buffer_size, frame.get()); assert(output_size <= buffer_size); - cout << "output size" << output_size << endl; auto file = fopen(filename.c_str(), "wb"); fwrite(buffer.data(), 1, make_u(output_size), file); @@ -140,22 +140,29 @@ static void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int int main(){ av_register_all(); + av_log_set_level(AV_LOG_QUIET); + // TODO: use boost::program_options string const database_directory = "database"; string const filename = "image.jpg"; string const output = "output.jpg"; int const h_tiles = 4 * 7; int const v_tiles = 3 * 7; + // NOTE: we could almost do the rest on one line ;D + // TODO: make every step parallel (this should be easy, except for the IO steps) auto const db = read_database(database_directory); - auto const mozaic = create_mozaic(db, filename, h_tiles, v_tiles); - save_mozaic(mozaic, output, h_tiles, v_tiles); + cout << colors::green("Read database: ") << db.size() << endl; - // debugging - for(int r = 0; r < v_tiles; ++r){ - for(int c = 0; c < h_tiles; ++c){ - cout << mozaic.at(make_pair(c, r)) << "\t"; - } - cout << endl; - } + auto const distances = calculate_all_distances(db, filename, h_tiles, v_tiles); + cout << colors::green("Calculated distances for ") << filename << endl; + + auto const best_indices = optimize(distances, 1); + cout << colors::green("Picked best distances") << endl; + + auto const mozaic = mozaic_fmap(best_indices, [&](auto i){ return db.filename(i); }); + cout << colors::green("Picked filenames") << endl; + + save_mozaic(mozaic, output); + cout << colors::green("Saved Mozaic to ") << output << endl; } diff --git a/src/needle.cpp b/src/needle.cpp deleted file mode 100644 index 0090391..0000000 --- a/src/needle.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include -#include -#include - -#include - -extern "C" { -#include -} - -#include -#include -#include - -using namespace std; -namespace fs = boost::filesystem; - -int main(){ - av_register_all(); - - string const database_directory = "database"; - string const filename = "needle.jpg"; - - image_database db; - - fs::path const directory(database_directory); - fs::directory_iterator eod; - for(fs::directory_iterator it(directory); it != eod; ++it){ - auto const path = it->path(); - auto const ext = path.extension(); - if(ext != ".png" && ext != ".jpg") continue; - - cout << colors::green("adding: ") << path.string() << endl; - db.add(path.string()); - } - - while(true){ - cout << "****************" << endl; - cout << colors::green("database: ") << db.size() << endl; - cout << colors::green("press enter to search match for: ") << filename << endl; - cin.ignore(); - auto const index = db.nearest_image(open_image(filename)); - cout << colors::green("match: ") << db.filename(index) << endl; - } -} -