Adds possibility to do asymmetric fingerprint comparison
Adds another metric (doesn't work yet) Adds more constness
This commit is contained in:
parent
328dcbc471
commit
224b7bfc0b
17 changed files with 247 additions and 163 deletions
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
file(GLOB sources "*.cpp")
|
file(GLOB_RECURSE sources "*.cpp")
|
||||||
file(GLOB headers "*.hpp")
|
file(GLOB_RECURSE headers "*.hpp")
|
||||||
|
|
||||||
set(libs avformat avcodec avutil swscale)
|
set(libs avformat avcodec avutil swscale)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace av {
|
||||||
// AVFrame related
|
// AVFrame related
|
||||||
using frame = std::unique_ptr<AVFrame, deleter<AVFrame>>;
|
using frame = std::unique_ptr<AVFrame, deleter<AVFrame>>;
|
||||||
frame frame_alloc();
|
frame frame_alloc();
|
||||||
frame frame_clone(frame const & f);
|
frame frame_clone(frame const & f); // creates a clone with the *same* buffer
|
||||||
AVPixelFormat get_format(frame const & f);
|
AVPixelFormat get_format(frame const & f);
|
||||||
|
|
||||||
// Allocator
|
// Allocator
|
||||||
|
|
16
lib/fingerprint_traits.hpp
Normal file
16
lib/fingerprint_traits.hpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include"av.hpp"
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
// A fingerprint is used to speed up comparison. However, this is done assymetrically. Only the
|
||||||
|
// compressed version is used in the database, but for comparison the full image can be used.
|
||||||
|
template <typename Fingerprint>
|
||||||
|
struct fingerprint_traits {
|
||||||
|
using fingerprint = Fingerprint;
|
||||||
|
using pre_fingerprint = decltype(fingerprint::pre_calculate(std::declval<av::frame>()));
|
||||||
|
|
||||||
|
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); }
|
||||||
|
};
|
4
lib/fingerprints.hpp
Normal file
4
lib/fingerprints.hpp
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fingerprints/wavelet.hpp"
|
||||||
|
#include "fingerprints/downscale.hpp"
|
35
lib/fingerprints/downscale.cpp
Normal file
35
lib/fingerprints/downscale.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include "downscale.hpp"
|
||||||
|
|
||||||
|
#include <image_io.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
downscale downscale::pre_calculate(const av::frame& frame){
|
||||||
|
// ffmpeg doesnt let us downscale all the way to 5 at once :(
|
||||||
|
auto const image = to_raw_rgb_image(frame, 5, 5);
|
||||||
|
|
||||||
|
downscale ret;
|
||||||
|
ret.data.assign(image.data.size(), 0);
|
||||||
|
|
||||||
|
std::copy(image.data.begin(), image.data.end(), ret.data.begin());
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
downscale downscale::calculate(const av::frame& frame){
|
||||||
|
return pre_calculate(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
double square(double x){
|
||||||
|
return x*x;
|
||||||
|
}
|
||||||
|
|
||||||
|
double downscale::distance_to(const downscale& fingerprint) const {
|
||||||
|
assert(data.size() == fingerprint.data.size());
|
||||||
|
|
||||||
|
double distance = 0;
|
||||||
|
for(size_t i = 0; i < data.size(); ++i){
|
||||||
|
distance += square(data[i] - fingerprint.data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
27
lib/fingerprints/downscale.hpp
Normal file
27
lib/fingerprints/downscale.hpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <av.hpp>
|
||||||
|
#include <fingerprint_traits.hpp>
|
||||||
|
|
||||||
|
#include <boost/serialization/access.hpp>
|
||||||
|
#include <boost/serialization/vector.hpp>
|
||||||
|
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct downscale {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
static downscale pre_calculate(av::frame const & frame);
|
||||||
|
static downscale calculate(av::frame const & frame);
|
||||||
|
double distance_to(downscale const & fingerprint) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int /*version*/){
|
||||||
|
ar & data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& out, downscale const & x);
|
|
@ -1,14 +1,37 @@
|
||||||
#include "fingerprint.hpp"
|
|
||||||
#include "wavelet.hpp"
|
#include "wavelet.hpp"
|
||||||
#include "utilities.hpp"
|
#include <wavelet.hpp>
|
||||||
|
#include <utilities.hpp>
|
||||||
|
#include <image_io.hpp>
|
||||||
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
rgb_wavelet_coefficients rgb_wavelet_coefficients::calculate(const raw_rgb_image& image){
|
static const int size = 512;
|
||||||
|
|
||||||
|
rgb_wavelet_coefficients::pre_fingerprint rgb_wavelet_coefficients::pre_calculate(av::frame const & frame) {
|
||||||
|
auto const image = to_raw_rgb_image(crop_to_square(frame), size, size);
|
||||||
|
rgb_wavelet_coefficients::pre_fingerprint ret;
|
||||||
|
|
||||||
|
// for every color
|
||||||
|
for(unsigned int color = 0; color < 3; ++color){
|
||||||
|
auto & vector = ret[color];
|
||||||
|
vector.assign(make_u(image.width() * image.height()), 0);
|
||||||
|
|
||||||
|
for(unsigned int n = 0; n < make_u(image.width() * image.height()); ++n){
|
||||||
|
vector[n] = 2.0 * image.data[3*n + color] / double(255) - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
wvlt::wavelet_2D(vector.data(), make_u(image.width()), make_u(image.height()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb_wavelet_coefficients rgb_wavelet_coefficients::calculate(av::frame const & frame){
|
||||||
|
auto const image = to_raw_rgb_image(crop_to_square(frame), size, size);
|
||||||
rgb_wavelet_coefficients ret;
|
rgb_wavelet_coefficients ret;
|
||||||
|
|
||||||
std::vector<double> vector(make_u(image.width() * image.height()));
|
std::vector<double> vector(make_u(image.width() * image.height()), 0);
|
||||||
|
|
||||||
// for every color
|
// for every color
|
||||||
for(unsigned int color = 0; color < 3; ++color){
|
for(unsigned int color = 0; color < 3; ++color){
|
||||||
|
@ -24,12 +47,12 @@ rgb_wavelet_coefficients rgb_wavelet_coefficients::calculate(const raw_rgb_image
|
||||||
auto copy = vector;
|
auto copy = vector;
|
||||||
for(auto & x : copy) x = std::abs(x);
|
for(auto & x : copy) x = std::abs(x);
|
||||||
|
|
||||||
auto n_coefficients = coefficient_array.size();
|
auto const n_coefficients = coefficient_array.size();
|
||||||
std::nth_element(copy.begin(), copy.begin() + n_coefficients, copy.end(), std::greater<double>());
|
std::nth_element(copy.begin(), copy.begin() + n_coefficients, copy.end(), std::greater<double>());
|
||||||
double threshold = copy[n_coefficients-1];
|
auto const threshold = copy[n_coefficients-1];
|
||||||
|
|
||||||
for(unsigned int n = 0; n < vector.size(); ++n){
|
for(unsigned int n = 0; n < vector.size(); ++n){
|
||||||
auto x = vector[n];
|
auto const x = vector[n];
|
||||||
if(std::abs(x) >= threshold) {
|
if(std::abs(x) >= threshold) {
|
||||||
coefficient_array[array_index++] = std::make_pair(n, x);
|
coefficient_array[array_index++] = std::make_pair(n, x);
|
||||||
}
|
}
|
||||||
|
@ -46,38 +69,16 @@ static double square(double x){
|
||||||
return x*x;
|
return x*x;
|
||||||
}
|
}
|
||||||
|
|
||||||
double rgb_wavelet_coefficients::distance_to(const rgb_wavelet_coefficients& y) const {
|
double rgb_wavelet_coefficients::distance_to(pre_fingerprint const & fingerprint) const {
|
||||||
double distance = 0;
|
double distance = 0;
|
||||||
|
|
||||||
for(unsigned int color = 0; color < 3; ++color){
|
for(unsigned int color = 0; color < 3; ++color){
|
||||||
unsigned int i = 0, j = 0;
|
auto const & coefficients = color == 0 ? reds : (color == 1 ? greens : blues);
|
||||||
auto& x_array = color == 0 ? reds : (color == 1 ? greens : blues);
|
|
||||||
auto& y_array = color == 0 ? y.reds : (color == 1 ? y.greens : y.blues);
|
|
||||||
|
|
||||||
// "merge"
|
for(auto&& p : coefficients){
|
||||||
while(i < x_array.size() && j < y_array.size()){
|
auto const x = p.second;
|
||||||
auto x_pair = x_array[i];
|
auto const y = fingerprint[color][p.first];
|
||||||
auto y_pair = y_array[j];
|
distance += square(x - y) - square(y);
|
||||||
|
|
||||||
if(x_pair.first == y_pair.first) {
|
|
||||||
distance += square(y_pair.second - x_pair.second);
|
|
||||||
++i;
|
|
||||||
++j;
|
|
||||||
} else if(x_pair.first < y_pair.first) {
|
|
||||||
distance += square(x_pair.second);
|
|
||||||
++i;
|
|
||||||
} else {
|
|
||||||
distance += square(y_pair.second);
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remaining part, either x or y
|
|
||||||
for(; i < x_array.size(); ++i){
|
|
||||||
distance += square(x_array[i].second);
|
|
||||||
}
|
|
||||||
for(; j < y_array.size(); ++j){
|
|
||||||
distance += square(y_array[j].second);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,10 +96,12 @@ std::ostream& operator<<(std::ostream& out, rgb_wavelet_coefficients const & x){
|
||||||
out << "rgb_wavelet_coefficients" << std::endl;
|
out << "rgb_wavelet_coefficients" << std::endl;
|
||||||
|
|
||||||
for(int color = 0; color < 3; ++color){
|
for(int color = 0; color < 3; ++color){
|
||||||
auto& coefficient_array = color == 0 ? x.reds : (color == 1 ? x.greens : x.blues);
|
auto const & coefficient_array = color == 0 ? x.reds : (color == 1 ? x.greens : x.blues);
|
||||||
|
|
||||||
out << '[' << coefficient_array[0];
|
out << '[' << coefficient_array[0];
|
||||||
for(unsigned int i = 1; i < coefficient_array.size(); ++i) out << ", " << coefficient_array[i];
|
for(unsigned int i = 1; i < coefficient_array.size(); ++i) {
|
||||||
|
out << ", " << coefficient_array[i];
|
||||||
|
}
|
||||||
out << ']' << std::endl;
|
out << ']' << std::endl;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "image_io.hpp"
|
#include <av.hpp>
|
||||||
|
#include <fingerprint_traits.hpp>
|
||||||
|
|
||||||
#include <boost/serialization/access.hpp>
|
#include <boost/serialization/access.hpp>
|
||||||
#include <boost/serialization/array.hpp>
|
#include <boost/serialization/array.hpp>
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace boost {
|
namespace boost {
|
||||||
namespace serialization {
|
namespace serialization {
|
||||||
|
@ -19,28 +21,18 @@ namespace boost {
|
||||||
} // namespace serialization
|
} // namespace serialization
|
||||||
} // namespace boost
|
} // namespace boost
|
||||||
|
|
||||||
// Default implementation
|
|
||||||
template <typename Fingerprint>
|
|
||||||
struct fingerprint_traits {
|
|
||||||
static Fingerprint calculate(raw_rgb_image const & image) {
|
|
||||||
return Fingerprint::calculate(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
static auto distance(Fingerprint const & x, Fingerprint const & y) {
|
|
||||||
return x.distance_to(y);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct rgb_wavelet_coefficients {
|
struct rgb_wavelet_coefficients {
|
||||||
// a double for (x, y) location represented in a single int
|
// a double for (x, y) location represented in a single int
|
||||||
using coefficient = std::pair<int, double>;
|
using coefficient = std::pair<int, double>;
|
||||||
|
using pre_fingerprint = std::array<std::vector<double>, 3>;
|
||||||
|
|
||||||
std::array<coefficient, 20> reds;
|
std::array<coefficient, 20> reds;
|
||||||
std::array<coefficient, 20> greens;
|
std::array<coefficient, 20> greens;
|
||||||
std::array<coefficient, 20> blues;
|
std::array<coefficient, 20> blues;
|
||||||
|
|
||||||
static rgb_wavelet_coefficients calculate(raw_rgb_image const & image);
|
static pre_fingerprint pre_calculate(av::frame const & frame);
|
||||||
double distance_to(rgb_wavelet_coefficients const & y) const;
|
static rgb_wavelet_coefficients calculate(av::frame const & frame);
|
||||||
|
double distance_to(pre_fingerprint const & fingerprint) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
|
@ -1,2 +0,0 @@
|
||||||
#include "image_database.hpp"
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <image_io.hpp>
|
#include <image_io.hpp>
|
||||||
#include <fingerprint.hpp>
|
#include <fingerprints.hpp>
|
||||||
|
|
||||||
#include <boost/serialization/access.hpp>
|
#include <boost/serialization/access.hpp>
|
||||||
#include <boost/serialization/vector.hpp>
|
#include <boost/serialization/vector.hpp>
|
||||||
|
@ -13,9 +13,9 @@ template <typename Fingerprint, typename Traits = fingerprint_traits<Fingerprint
|
||||||
struct image_database {
|
struct image_database {
|
||||||
using index = size_t;
|
using index = size_t;
|
||||||
|
|
||||||
void add(std::string filename){
|
void add(std::string const & filename){
|
||||||
auto image = open_as_rgb(filename);
|
auto && image = open_image(filename);
|
||||||
auto fingerprint = Traits::calculate(image);
|
auto && fingerprint = Traits::calculate(image);
|
||||||
|
|
||||||
filenames.push_back(filename);
|
filenames.push_back(filename);
|
||||||
fingerprints.push_back(fingerprint);
|
fingerprints.push_back(fingerprint);
|
||||||
|
@ -33,14 +33,14 @@ struct image_database {
|
||||||
return fingerprints.size();
|
return fingerprints.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
index nearest_image(raw_rgb_image const & image) const {
|
index nearest_image(av::frame const & image) const {
|
||||||
auto fingerprint = Traits::calculate(image);
|
const auto && pre_fingerprint = Traits::pre_calculate(image);
|
||||||
|
|
||||||
index best_index = 0;
|
index best_index = 0;
|
||||||
auto best_distance = Traits::distance(fingerprint, fingerprints[0]);
|
auto && best_distance = Traits::distance(fingerprints[0], pre_fingerprint);
|
||||||
|
|
||||||
for(index i = 1; i < fingerprints.size(); ++i){
|
for(index i = 1; i < fingerprints.size(); ++i){
|
||||||
auto distance = Traits::distance(fingerprint, fingerprints[i]);
|
const auto && distance = Traits::distance(fingerprints[i], pre_fingerprint);
|
||||||
if(distance < best_distance) {
|
if(distance < best_distance) {
|
||||||
best_distance = distance;
|
best_distance = distance;
|
||||||
best_index = i;
|
best_index = i;
|
||||||
|
|
|
@ -103,6 +103,10 @@ void crop_to_square(av::frame& frame){
|
||||||
if(ret < 0) throw std::runtime_error("boem crop");
|
if(ret < 0) throw std::runtime_error("boem crop");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
av::frame crop_to_square(const av::frame& frame){
|
||||||
|
return crop_to_square(av::frame_clone(frame));
|
||||||
|
}
|
||||||
|
|
||||||
av::frame crop_to_square(av::frame && frame){
|
av::frame crop_to_square(av::frame && frame){
|
||||||
crop_to_square(frame);
|
crop_to_square(frame);
|
||||||
return std::move(frame);
|
return std::move(frame);
|
||||||
|
@ -120,13 +124,14 @@ raw_rgb_image to_raw_rgb_image(av::frame const & frame, int new_width, int new_h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void apply_to_tiles(const std::string& filename, int h_tiles, int v_tiles, std::function<void (int, int, raw_rgb_image const &)> fun){
|
void apply_to_tiles(std::string const & filename, int h_tiles, int v_tiles, std::function<void(int, int, av::frame const &)> fun) {
|
||||||
auto org_frame = open_image(filename);
|
auto org_frame = open_image(filename);
|
||||||
|
|
||||||
// create clone to crop
|
// create clone to crop
|
||||||
av::frame cropped_frame = av::frame_clone(org_frame);
|
av::frame cropped_frame = av::frame_clone(org_frame);
|
||||||
|
|
||||||
// create raw buffer for the callback
|
// create raw buffer for the callback
|
||||||
|
// TODO: do not scale the cropped region
|
||||||
raw_rgb_image image(512, 512);
|
raw_rgb_image image(512, 512);
|
||||||
|
|
||||||
// create the tiles
|
// create the tiles
|
||||||
|
@ -144,7 +149,61 @@ void apply_to_tiles(const std::string& filename, int h_tiles, int v_tiles, std::
|
||||||
sws_scale (context, {cropped_frame->data}, {cropped_frame->linesize}, 0, cropped_frame->height, {image.frame->data}, {image.frame->linesize});
|
sws_scale (context, {cropped_frame->data}, {cropped_frame->linesize}, 0, cropped_frame->height, {image.frame->data}, {image.frame->linesize});
|
||||||
sws_freeContext(context);
|
sws_freeContext(context);
|
||||||
|
|
||||||
fun(c, r, image);
|
fun(c, r, image.frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void save_as_jpg(av::frame const & frame, std::string const & filename){
|
||||||
|
const auto pix_fmt = AV_PIX_FMT_YUVJ444P;
|
||||||
|
const auto codec_id= AV_CODEC_ID_MJPEG;
|
||||||
|
|
||||||
|
// Convert
|
||||||
|
int tile_width = 800;
|
||||||
|
int tile_height = 600;
|
||||||
|
|
||||||
|
int h_tiles = 8;
|
||||||
|
int v_tiles = 6;
|
||||||
|
|
||||||
|
std::vector<uint8_t, av::allocator<uint8_t>> data(make_u(avpicture_get_size(pix_fmt, h_tiles * tile_width, v_tiles * tile_height)), 0);
|
||||||
|
av::frame converted_frame = av::frame_alloc();
|
||||||
|
avpicture_fill(reinterpret_cast<AVPicture*>(converted_frame.get()), data.data(), pix_fmt, h_tiles * tile_width, v_tiles * tile_height);
|
||||||
|
converted_frame->width = h_tiles * tile_width;
|
||||||
|
converted_frame->height = v_tiles * tile_height;
|
||||||
|
converted_frame->format = pix_fmt;
|
||||||
|
|
||||||
|
auto const sws_context = sws_getContext(frame->width, frame->height, av::get_format(frame), tile_width, tile_height, av::get_format(converted_frame), 0, nullptr, nullptr, nullptr);
|
||||||
|
if(!sws_context) throw std::runtime_error("boem sws context");
|
||||||
|
|
||||||
|
av::frame cropped_frame = av::frame_clone(converted_frame);
|
||||||
|
for(int r = 0; r < v_tiles; ++r) {
|
||||||
|
for(int c = 0; c < h_tiles; ++c){
|
||||||
|
av_picture_crop(reinterpret_cast<AVPicture*>(cropped_frame.get()), reinterpret_cast<AVPicture*>(converted_frame.get()), av::get_format(converted_frame), r * tile_height, c * tile_width);
|
||||||
|
sws_scale (sws_context, {frame->data}, {frame->linesize}, 0, frame->height, {cropped_frame->data}, {cropped_frame->linesize});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sws_freeContext(sws_context);
|
||||||
|
|
||||||
|
// Encode
|
||||||
|
auto const codec = avcodec_find_encoder(codec_id);
|
||||||
|
if(!codec) throw av::error("Could not find codec");
|
||||||
|
|
||||||
|
auto codec_ctx = std::unique_ptr<AVCodecContext, av::deleter<AVCodecContext>>(avcodec_alloc_context3(codec), [](auto x){ avcodec_free_context(&x); });
|
||||||
|
if(!codec_ctx) throw av::error("Could not allocate codec context");
|
||||||
|
|
||||||
|
codec_ctx->pix_fmt = pix_fmt;
|
||||||
|
codec_ctx->width = converted_frame->width;
|
||||||
|
codec_ctx->height = converted_frame->height;
|
||||||
|
codec_ctx->time_base = av_make_q(1, 1);
|
||||||
|
auto const opened_codec = av::codec_open(codec_ctx.get(), codec, nullptr);
|
||||||
|
|
||||||
|
auto const buffer_size = avpicture_get_size(pix_fmt, codec_ctx->width, codec_ctx->height);
|
||||||
|
std::vector<uint8_t> buffer(make_u(buffer_size), 0);
|
||||||
|
auto const output_size = avcodec_encode_video(codec_ctx.get(), buffer.data(), buffer_size, converted_frame.get());
|
||||||
|
assert(output_size <= buffer_size);
|
||||||
|
|
||||||
|
auto const file = fopen(filename.c_str(), "wb");
|
||||||
|
fwrite(buffer.data(), 1, make_u(output_size), file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ av::frame open_image(std::string const & filename);
|
||||||
|
|
||||||
// crops to the bottom right square (cheap operation)
|
// crops to the bottom right square (cheap operation)
|
||||||
void crop_to_square(av::frame & frame);
|
void crop_to_square(av::frame & frame);
|
||||||
|
av::frame crop_to_square(av::frame const & frame);
|
||||||
av::frame crop_to_square(av::frame && frame);
|
av::frame crop_to_square(av::frame && frame);
|
||||||
|
|
||||||
// converts and resizes
|
// converts and resizes
|
||||||
|
@ -39,4 +40,7 @@ inline raw_rgb_image open_as_rgb(const std::string &filename){
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply function to every tile, fun :: Column, Row, Image -> Void
|
// apply function to every tile, fun :: Column, Row, Image -> Void
|
||||||
void apply_to_tiles(std::string const & filename, int h_tiles, int v_tiles, std::function<void(int, int, raw_rgb_image const &)> fun);
|
void apply_to_tiles(std::string const & filename, int h_tiles, int v_tiles, std::function<void(int, int, av::frame const &)> fun);
|
||||||
|
|
||||||
|
// does what you think it does
|
||||||
|
void save_as_jpg(av::frame const & frame, std::string const & filename);
|
||||||
|
|
|
@ -50,6 +50,7 @@ int main(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
save_as_ppm(image, "output.ppm");
|
// TODO: save as jpg
|
||||||
|
save_as_jpg(image.frame, "output.ppm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <fingerprint.hpp>
|
#include <fingerprints.hpp>
|
||||||
|
#include <image_io.hpp>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
@ -10,10 +11,11 @@ using namespace std;
|
||||||
|
|
||||||
int main(){
|
int main(){
|
||||||
av_register_all();
|
av_register_all();
|
||||||
auto image = open_as_rgb("test.jpg");
|
auto const image = open_image("test.jpg");
|
||||||
|
|
||||||
rgb_wavelet_coefficients x = rgb_wavelet_coefficients::calculate(image);
|
auto const x = rgb_wavelet_coefficients::calculate(image);
|
||||||
|
auto const y = rgb_wavelet_coefficients::pre_calculate(image);
|
||||||
|
|
||||||
cout << x << endl;
|
cout << x << endl;
|
||||||
cout << x.distance_to(x) << endl;
|
cout << x.distance_to(y) << endl;
|
||||||
}
|
}
|
||||||
|
|
51
src/main.cpp
51
src/main.cpp
|
@ -1,7 +1,7 @@
|
||||||
#include <image_io.hpp>
|
#include <image_io.hpp>
|
||||||
#include <wavelet.hpp>
|
#include <wavelet.hpp>
|
||||||
#include <image_database.hpp>
|
#include <image_database.hpp>
|
||||||
#include <fingerprint.hpp>
|
#include <fingerprints.hpp>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/archive/binary_oarchive.hpp>
|
#include <boost/archive/binary_oarchive.hpp>
|
||||||
|
@ -21,6 +21,9 @@ extern "C" {
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
|
static const int tile_width = 128;
|
||||||
|
static const int tile_height = 128;
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
namespace ar = boost::archive;
|
namespace ar = boost::archive;
|
||||||
|
@ -29,15 +32,15 @@ using Database = image_database<rgb_wavelet_coefficients>;
|
||||||
using Mozaic = map<pair<int, int>, string>;
|
using Mozaic = map<pair<int, int>, string>;
|
||||||
|
|
||||||
Database read_database(string const & database_directory){
|
Database read_database(string const & database_directory){
|
||||||
image_database<rgb_wavelet_coefficients> db;
|
Database db;
|
||||||
auto database_file = database_directory + ".db";
|
auto const database_file = database_directory + ".db";
|
||||||
|
|
||||||
if (!boost::filesystem::exists(database_file)){
|
if (!boost::filesystem::exists(database_file)){
|
||||||
fs::path directory(database_directory);
|
fs::path const directory(database_directory);
|
||||||
fs::directory_iterator eod;
|
fs::directory_iterator eod;
|
||||||
for(fs::directory_iterator it(directory); it != eod; ++it){
|
for(fs::directory_iterator it(directory); it != eod; ++it){
|
||||||
auto && path = it->path();
|
auto const path = it->path();
|
||||||
auto ext = path.extension();
|
auto const ext = path.extension();
|
||||||
if(ext != ".png" && ext != ".jpg") continue;
|
if(ext != ".png" && ext != ".jpg") continue;
|
||||||
|
|
||||||
cout << colors::green("adding: ") << path.string() << endl;
|
cout << colors::green("adding: ") << path.string() << endl;
|
||||||
|
@ -50,7 +53,6 @@ Database read_database(string const & database_directory){
|
||||||
} else {
|
} else {
|
||||||
ifstream file(database_file);
|
ifstream file(database_file);
|
||||||
ar::binary_iarchive archive(file);
|
ar::binary_iarchive archive(file);
|
||||||
// read class state from archive
|
|
||||||
archive >> db;
|
archive >> db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +61,10 @@ Database read_database(string const & database_directory){
|
||||||
}
|
}
|
||||||
|
|
||||||
Mozaic create_mozaic(Database const & db, string const & filename, int h_tiles, int v_tiles){
|
Mozaic create_mozaic(Database const & db, string const & filename, int h_tiles, int v_tiles){
|
||||||
map<pair<int, int>, string> mozaic;
|
Mozaic mozaic;
|
||||||
|
|
||||||
apply_to_tiles("image.jpg", h_tiles, v_tiles, [&](int c, int r, raw_rgb_image const & image){
|
apply_to_tiles("image.jpg", h_tiles, v_tiles, [&](int c, int r, av::frame const & frame){
|
||||||
auto index = db.nearest_image(image);
|
auto const index = db.nearest_image(frame);
|
||||||
cout << colors::red("tile ") << c << ", " << r << ": " << db.filename(index) << endl;
|
cout << colors::red("tile ") << c << ", " << r << ": " << db.filename(index) << endl;
|
||||||
mozaic[make_pair(c, r)] = db.filename(index);
|
mozaic[make_pair(c, r)] = db.filename(index);
|
||||||
});
|
});
|
||||||
|
@ -71,11 +73,8 @@ Mozaic create_mozaic(Database const & db, string const & filename, int h_tiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int v_tiles){
|
void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int v_tiles){
|
||||||
const auto pix_fmt = AV_PIX_FMT_YUVJ444P;
|
auto const pix_fmt = AV_PIX_FMT_YUVJ444P;
|
||||||
const auto codec_id= AV_CODEC_ID_MJPEG;
|
auto const codec_id= AV_CODEC_ID_MJPEG;
|
||||||
|
|
||||||
int tile_width = 128;
|
|
||||||
int tile_height = 128;
|
|
||||||
|
|
||||||
// Open all files we need
|
// Open all files we need
|
||||||
map<string, av::frame> frames;
|
map<string, av::frame> frames;
|
||||||
|
@ -85,8 +84,8 @@ void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int v_tile
|
||||||
frames.emplace(x.second, crop_to_square(open_image(x.second)));
|
frames.emplace(x.second, crop_to_square(open_image(x.second)));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto total_width = h_tiles * tile_width;
|
auto const total_width = h_tiles * tile_width;
|
||||||
auto total_height = v_tiles * tile_height;
|
auto const total_height = v_tiles * tile_height;
|
||||||
|
|
||||||
// Create output frame
|
// Create output frame
|
||||||
std::vector<uint8_t, av::allocator<uint8_t>> data(make_u(avpicture_get_size(pix_fmt, total_width, total_height)), 0);
|
std::vector<uint8_t, av::allocator<uint8_t>> data(make_u(avpicture_get_size(pix_fmt, total_width, total_height)), 0);
|
||||||
|
@ -128,9 +127,9 @@ void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int v_tile
|
||||||
codec_ctx->time_base = av_make_q(1, 1);
|
codec_ctx->time_base = av_make_q(1, 1);
|
||||||
auto opened_codec = av::codec_open(codec_ctx.get(), codec, nullptr);
|
auto opened_codec = av::codec_open(codec_ctx.get(), codec, nullptr);
|
||||||
|
|
||||||
const auto buffer_size = avpicture_get_size(pix_fmt, codec_ctx->width, codec_ctx->height);
|
auto const buffer_size = avpicture_get_size(pix_fmt, codec_ctx->width, codec_ctx->height);
|
||||||
std::vector<uint8_t> buffer(make_u(buffer_size), 0);
|
std::vector<uint8_t> buffer(make_u(buffer_size), 0);
|
||||||
auto output_size = avcodec_encode_video(codec_ctx.get(), buffer.data(), buffer_size, frame.get());
|
auto const output_size = avcodec_encode_video(codec_ctx.get(), buffer.data(), buffer_size, frame.get());
|
||||||
assert(output_size <= buffer_size);
|
assert(output_size <= buffer_size);
|
||||||
cout << "output size" << output_size << endl;
|
cout << "output size" << output_size << endl;
|
||||||
|
|
||||||
|
@ -142,14 +141,14 @@ void save_mozaic(Mozaic const & mozaic, string filename, int h_tiles, int v_tile
|
||||||
int main(){
|
int main(){
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
|
||||||
string database_directory = "database";
|
string const database_directory = "database";
|
||||||
string filename = "image.jpg";
|
string const filename = "image.jpg";
|
||||||
string output = "output.jpg";
|
string const output = "output.jpg";
|
||||||
int h_tiles = 4 * 13;
|
int const h_tiles = 4 * 7;
|
||||||
int v_tiles = 3 * 13;
|
int const v_tiles = 3 * 7;
|
||||||
|
|
||||||
const auto db = read_database(database_directory);
|
auto const db = read_database(database_directory);
|
||||||
const auto mozaic = create_mozaic(db, filename, h_tiles, v_tiles);
|
auto const mozaic = create_mozaic(db, filename, h_tiles, v_tiles);
|
||||||
save_mozaic(mozaic, output, h_tiles, v_tiles);
|
save_mozaic(mozaic, output, h_tiles, v_tiles);
|
||||||
|
|
||||||
// debugging
|
// debugging
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include <image_io.hpp>
|
#include <image_io.hpp>
|
||||||
#include <wavelet.hpp>
|
#include <wavelet.hpp>
|
||||||
#include <image_database.hpp>
|
#include <image_database.hpp>
|
||||||
#include <fingerprint.hpp>
|
#include <fingerprints.hpp>
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
@ -19,16 +19,16 @@ namespace fs = boost::filesystem;
|
||||||
int main(){
|
int main(){
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
|
||||||
string database_directory = "database";
|
string const database_directory = "database";
|
||||||
string filename = "needle.jpg";
|
string const filename = "needle.jpg";
|
||||||
|
|
||||||
image_database<rgb_wavelet_coefficients> db;
|
image_database<rgb_wavelet_coefficients> db;
|
||||||
|
|
||||||
fs::path directory(database_directory);
|
fs::path const directory(database_directory);
|
||||||
fs::directory_iterator eod;
|
fs::directory_iterator eod;
|
||||||
for(fs::directory_iterator it(directory); it != eod; ++it){
|
for(fs::directory_iterator it(directory); it != eod; ++it){
|
||||||
auto && path = it->path();
|
auto const path = it->path();
|
||||||
auto ext = path.extension();
|
auto const ext = path.extension();
|
||||||
if(ext != ".png" && ext != ".jpg") continue;
|
if(ext != ".png" && ext != ".jpg") continue;
|
||||||
|
|
||||||
cout << colors::green("adding: ") << path.string() << endl;
|
cout << colors::green("adding: ") << path.string() << endl;
|
||||||
|
@ -40,7 +40,7 @@ int main(){
|
||||||
cout << colors::green("database: ") << db.size() << endl;
|
cout << colors::green("database: ") << db.size() << endl;
|
||||||
cout << colors::green("press enter to search match for: ") << filename << endl;
|
cout << colors::green("press enter to search match for: ") << filename << endl;
|
||||||
cin.ignore();
|
cin.ignore();
|
||||||
auto index = db.nearest_image(open_as_rgb(filename));
|
auto const index = db.nearest_image(open_image(filename));
|
||||||
cout << colors::green("match: ") << db.filename(index) << endl;
|
cout << colors::green("match: ") << db.filename(index) << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,67 +9,11 @@ extern "C" {
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
static void save_as_jpg(av::frame const & frame, std::string const & filename){
|
|
||||||
const auto pix_fmt = AV_PIX_FMT_YUVJ444P;
|
|
||||||
const auto codec_id= AV_CODEC_ID_MJPEG;
|
|
||||||
|
|
||||||
// Convert
|
|
||||||
int tile_width = 800;
|
|
||||||
int tile_height = 600;
|
|
||||||
|
|
||||||
int h_tiles = 8;
|
|
||||||
int v_tiles = 6;
|
|
||||||
|
|
||||||
std::vector<uint8_t, av::allocator<uint8_t>> data(make_u(avpicture_get_size(pix_fmt, h_tiles * tile_width, v_tiles * tile_height)), 0);
|
|
||||||
av::frame converted_frame = av::frame_alloc();
|
|
||||||
avpicture_fill(reinterpret_cast<AVPicture*>(converted_frame.get()), data.data(), pix_fmt, h_tiles * tile_width, v_tiles * tile_height);
|
|
||||||
converted_frame->width = h_tiles * tile_width;
|
|
||||||
converted_frame->height = v_tiles * tile_height;
|
|
||||||
converted_frame->format = pix_fmt;
|
|
||||||
|
|
||||||
auto sws_context = sws_getContext(frame->width, frame->height, av::get_format(frame), tile_width, tile_height, av::get_format(converted_frame), 0, nullptr, nullptr, nullptr);
|
|
||||||
if(!sws_context) throw std::runtime_error("boem sws context");
|
|
||||||
|
|
||||||
av::frame cropped_frame = av::frame_clone(converted_frame);
|
|
||||||
for(int r = 0; r < v_tiles; ++r) {
|
|
||||||
for(int c = 0; c < h_tiles; ++c){
|
|
||||||
av_picture_crop(reinterpret_cast<AVPicture*>(cropped_frame.get()), reinterpret_cast<AVPicture*>(converted_frame.get()), av::get_format(converted_frame), r * tile_height, c * tile_width);
|
|
||||||
sws_scale (sws_context, {frame->data}, {frame->linesize}, 0, frame->height, {cropped_frame->data}, {cropped_frame->linesize});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sws_freeContext(sws_context);
|
|
||||||
|
|
||||||
// Encode
|
|
||||||
auto codec = avcodec_find_encoder(codec_id);
|
|
||||||
if(!codec) throw av::error("Could not find codec");
|
|
||||||
|
|
||||||
auto codec_ctx = std::unique_ptr<AVCodecContext, av::deleter<AVCodecContext>>(avcodec_alloc_context3(codec), [](auto x){ avcodec_free_context(&x); });
|
|
||||||
if(!codec_ctx) throw av::error("Could not allocate codec context");
|
|
||||||
|
|
||||||
codec_ctx->pix_fmt = pix_fmt;
|
|
||||||
codec_ctx->width = converted_frame->width;
|
|
||||||
codec_ctx->height = converted_frame->height;
|
|
||||||
codec_ctx->time_base = av_make_q(1, 1);
|
|
||||||
auto opened_codec = av::codec_open(codec_ctx.get(), codec, nullptr);
|
|
||||||
|
|
||||||
const auto buffer_size = avpicture_get_size(pix_fmt, codec_ctx->width, codec_ctx->height);
|
|
||||||
std::vector<uint8_t> buffer(make_u(buffer_size), 0);
|
|
||||||
auto output_size = avcodec_encode_video(codec_ctx.get(), buffer.data(), buffer_size, converted_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);
|
|
||||||
fclose(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(){
|
int main(){
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
|
||||||
while(true){
|
while(true){
|
||||||
auto image = open_image("needle.png");
|
auto const image = open_image("needle.png");
|
||||||
save_as_jpg(image, "output.jpg");
|
save_as_jpg(image, "output.jpg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue