Adds a mozaic datatype. Adds slack to mozaics. Gets rid of libav logging.
This commit is contained in:
parent
dd6e1abb49
commit
e206b3f4a8
5 changed files with 99 additions and 99 deletions
|
@ -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<fingerprint const &>(), std::declval<pre_fingerprint const &>()));
|
||||
};
|
||||
|
|
|
@ -13,6 +13,10 @@ template <typename Fingerprint, typename Traits = fingerprint_traits<Fingerprint
|
|||
struct image_database {
|
||||
using index = size_t;
|
||||
|
||||
auto filename(index i) const { return filenames[i]; }
|
||||
auto fingerprint(index i) const { return fingerprints[i]; }
|
||||
auto size() const { return fingerprints.size(); }
|
||||
|
||||
void add(std::string const & filename){
|
||||
auto && image = open_image(filename);
|
||||
auto && fingerprint = Traits::calculate(image);
|
||||
|
@ -21,33 +25,16 @@ struct image_database {
|
|||
fingerprints.push_back(fingerprint);
|
||||
}
|
||||
|
||||
std::string filename(index i) const {
|
||||
return filenames.at(i);
|
||||
}
|
||||
//! returns a list of distances along with their index
|
||||
auto distances_for_image(av::frame const & image) const {
|
||||
std::vector<std::pair<typename Traits::distance_type, index>> 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:
|
||||
|
|
51
lib/mozaic.hpp
Normal file
51
lib/mozaic.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
template <typename T>
|
||||
struct Mozaic {
|
||||
Mozaic(int h_tiles_, int v_tiles_)
|
||||
: h_tiles(h_tiles_)
|
||||
, v_tiles(v_tiles_)
|
||||
, tiles(v_tiles, std::vector<T>(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<std::vector<T>> tiles;
|
||||
};
|
||||
|
||||
template <typename F, typename T>
|
||||
auto mozaic_fmap(Mozaic<T> const & mozaic, F&& f){
|
||||
using return_type = decltype(f(mozaic[0][0]));
|
||||
Mozaic<return_type> 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 <typename Distance>
|
||||
auto optimize(Mozaic<std::vector<Distance>> distances, int slack){
|
||||
std::random_device rd;
|
||||
std::uniform_int_distribution<int> 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;
|
||||
});
|
||||
}
|
61
src/main.cpp
61
src/main.cpp
|
@ -2,6 +2,7 @@
|
|||
#include <wavelet.hpp>
|
||||
#include <image_database.hpp>
|
||||
#include <fingerprints.hpp>
|
||||
#include <mozaic.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/archive/binary_oarchive.hpp>
|
||||
|
@ -29,7 +30,7 @@ namespace fs = boost::filesystem;
|
|||
namespace ar = boost::archive;
|
||||
|
||||
using Database = image_database<downscale>;
|
||||
using Mozaic = map<pair<int, int>, string>;
|
||||
using Mozaiq = Mozaic<string>;
|
||||
|
||||
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<av::frame const &>()));
|
||||
Mozaic<return_type> 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<string, av::frame> 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<uint8_t, av::allocator<uint8_t>> 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<AVPicture*>(frame_part.get()), reinterpret_cast<AVPicture*>(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<uint8_t> 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;
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
#include <image_io.hpp>
|
||||
#include <wavelet.hpp>
|
||||
#include <image_database.hpp>
|
||||
#include <fingerprints.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
using namespace std;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
int main(){
|
||||
av_register_all();
|
||||
|
||||
string const database_directory = "database";
|
||||
string const filename = "needle.jpg";
|
||||
|
||||
image_database<rgb_wavelet_coefficients> 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;
|
||||
}
|
||||
}
|
||||
|
Reference in a new issue