diff --git a/.gitmodules b/.gitmodules index e46a00a..7e7534d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/ThreatExchange"] path = lib/ThreatExchange url = https://github.com/facebook/ThreatExchange +[submodule "lib/stb"] + path = lib/stb + url = https://github.com/nothings/stb diff --git a/029a_-_Survival_of_the_Idiots_349.jpg b/029a_-_Survival_of_the_Idiots_349.jpg new file mode 100755 index 0000000..5a9cdbf Binary files /dev/null and b/029a_-_Survival_of_the_Idiots_349.jpg differ diff --git a/15731915.png b/15731915.png new file mode 100644 index 0000000..72d883e Binary files /dev/null and b/15731915.png differ diff --git a/471289cde2490c80f60d5e85bcdfb6da.gif b/471289cde2490c80f60d5e85bcdfb6da.gif new file mode 100644 index 0000000..4857c6a Binary files /dev/null and b/471289cde2490c80f60d5e85bcdfb6da.gif differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ea4f86..3512621 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(image-gp-6 VERSION 0.0.3) +project(image-gp-6 VERSION 0.0.4) include(FetchContent) @@ -11,16 +11,21 @@ set(CMAKE_CXX_STANDARD 17) add_subdirectory(lib/blt-gp) +find_package( OpenCV REQUIRED ) + include_directories(include/) +include_directories(lib/stb) include_directories(lib/ThreatExchange) +include_directories( ${OpenCV_INCLUDE_DIRS} ) + file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") add_executable(image-gp-6 ${PROJECT_BUILD_FILES}) -target_compile_options(image-gp-6 PRIVATE -Wall -Wextra -Werror -Wpedantic -Wno-comment) -target_link_options(image-gp-6 PRIVATE -Wall -Wextra -Werror -Wpedantic -Wno-comment) +target_compile_options(image-gp-6 PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) +target_link_options(image-gp-6 PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) -target_link_libraries(image-gp-6 PRIVATE BLT blt-gp) +target_link_libraries(image-gp-6 PRIVATE BLT blt-gp ${OpenCV_LIBS}) if (${ENABLE_ADDRSAN} MATCHES ON) target_compile_options(image-gp-6 PRIVATE -fsanitize=address) diff --git a/Rolex_De_Grande_-_Joo.png b/Rolex_De_Grande_-_Joo.png new file mode 100644 index 0000000..ecd74ed Binary files /dev/null and b/Rolex_De_Grande_-_Joo.png differ diff --git a/Screenshot_20240715_005200_Clash_of_Clans.jpg b/Screenshot_20240715_005200_Clash_of_Clans.jpg new file mode 100644 index 0000000..ac8d646 Binary files /dev/null and b/Screenshot_20240715_005200_Clash_of_Clans.jpg differ diff --git a/cringey.png b/cringey.png new file mode 100644 index 0000000..abca59b Binary files /dev/null and b/cringey.png differ diff --git a/lib/stb b/lib/stb new file mode 160000 index 0000000..013ac3b --- /dev/null +++ b/lib/stb @@ -0,0 +1 @@ +Subproject commit 013ac3beddff3dbffafd5177e7972067cd2b5083 diff --git a/my_pride_flag.png b/my_pride_flag.png new file mode 100644 index 0000000..4673f49 Binary files /dev/null and b/my_pride_flag.png differ diff --git a/noah.png b/noah.png new file mode 100644 index 0000000..832fdeb Binary files /dev/null and b/noah.png differ diff --git a/silly.png b/silly.png new file mode 100644 index 0000000..2e1fcd7 Binary files /dev/null and b/silly.png differ diff --git a/src/main.cpp b/src/main.cpp index 1d2a211..3303cde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,88 +15,181 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_PERLIN_IMPLEMENTATION + #include #include #include #include +#include +#include +#include +#include +#include +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/imgproc.hpp" +#include -static constexpr long SEED = 41912; +static const blt::u64 SEED = std::random_device()(); +static constexpr long IMAGE_SIZE = 128; +static constexpr blt::size_t CHANNELS = 3; +static constexpr blt::size_t DATA_SIZE = IMAGE_SIZE * IMAGE_SIZE; struct context { float x, y; }; -std::array fitness_cases; +struct image_t +{ + std::array image_data; +}; + +struct full_image_t +{ + std::array image_data; + + void load(const std::string& path) + { + int width, height, channels; + auto data = stbi_load(path.c_str(), &width, &height, &channels, CHANNELS); + + stbir_resize_uint8_linear(data, width, height, 0, image_data.data(), IMAGE_SIZE, IMAGE_SIZE, 0, static_cast(CHANNELS)); + + stbi_image_free(data); + } + + void save(const std::string& str) + { + stbi_write_png(str.c_str(), IMAGE_SIZE, IMAGE_SIZE, CHANNELS, image_data.data(), 0); + } +}; + +using fitness_data_t = std::array; + +fitness_data_t fitness_red; +fitness_data_t fitness_green; +fitness_data_t fitness_blue; +full_image_t base_data; +full_image_t found_data; + +cv::Mat base_image_hsv; + +int h_bins = 50, s_bins = 60; +int histSize[] = { h_bins, s_bins }; + +// hue varies from 0 to 179, saturation from 0 to 255 +float h_ranges[] = { 0, 180 }; +float s_ranges[] = { 0, 256 }; + +const float* ranges[] = { h_ranges, s_ranges }; + +// Use the 0-th and 1-st channels +int channels[] = { 0, 1, 2 }; + +cv::Mat base_image_hist; blt::gp::prog_config_t config = blt::gp::prog_config_t() .set_initial_min_tree_size(2) .set_initial_max_tree_size(6) - .set_elite_count(0) + .set_elite_count(1) .set_max_generations(50) - .set_pop_size(500) + .set_mutation_chance(0.4) + .set_crossover_chance(0.9) + .set_pop_size(50) .set_thread_count(0); blt::gp::type_provider type_system; -blt::gp::gp_program program{type_system, SEED, config}; +blt::gp::gp_program program_red{type_system, SEED, config}; +blt::gp::gp_program program_green{type_system, SEED, config}; +blt::gp::gp_program program_blue{type_system, SEED, config}; -blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); -blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub"); -blt::gp::operation_t mul([](float a, float b) { return a * b; }, "mul"); -blt::gp::operation_t pro_div([](float a, float b) { return b == 0.0f ? 1.0f : a / b; }, "div"); -blt::gp::operation_t op_sin([](float a) { return std::sin(a); }, "sin"); -blt::gp::operation_t op_cos([](float a) { return std::cos(a); }, "cos"); -blt::gp::operation_t op_exp([](float a) { return std::exp(a); }, "exp"); -blt::gp::operation_t op_log([](float a) { return a == 0.0f ? 0.0f : std::log(a); }, "log"); - -blt::gp::operation_t lit([]() { - return program.get_random().get_float(-320.0f, 320.0f); -}, "lit"); -blt::gp::operation_t op_x([](const context& context) { - return context.x; -}, "x"); - -constexpr auto fitness_function = [](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t) { - constexpr double value_cutoff = 1.e15; - for (auto& fitness_case : fitness_cases) - { - auto diff = std::abs(fitness_case.y - current_tree.get_evaluation_value(&fitness_case)); - if (diff < value_cutoff) - { - fitness.raw_fitness += diff; - if (diff < 0.01) - fitness.hits++; - } else - fitness.raw_fitness += value_cutoff; - } - fitness.standardized_fitness = fitness.raw_fitness; - fitness.adjusted_fitness = 1.0 / (1.0 + fitness.standardized_fitness); - //BLT_TRACE("fitness: %lf raw: %lf", fitness.adjusted_fitness, fitness.raw_fitness); -}; - -float example_function(float x) +template +void create_program(blt::gp::gp_program& program) { - return x * x * x * x + x * x * x + x * x + x; -} - -int main() -{ - BLT_INFO("Starting BLT-GP Symbolic Regression Example"); - BLT_START_INTERVAL("Symbolic Regression", "Main"); - BLT_DEBUG("Setup Fitness cases"); - for (auto& fitness_case : fitness_cases) - { - constexpr float range = 10; - constexpr float half_range = range / 2.0; - auto x = program.get_random().get_float(-half_range, half_range); - auto y = example_function(x); - fitness_case = {x, y}; - } + static blt::gp::operation_t add([](float a, float b) { return a + b; }, "add"); + static blt::gp::operation_t sub([](float a, float b) { return a - b; }, "sub"); + static blt::gp::operation_t mul([](float a, float b) { return a * b; }, "mul"); + static blt::gp::operation_t pro_div([](float a, float b) { return b == 0.0f ? 1.0f : a / b; }, "div"); + static blt::gp::operation_t op_sin([](float a) { return std::sin(a); }, "sin"); + static blt::gp::operation_t op_cos([](float a) { return std::cos(a); }, "cos"); + static blt::gp::operation_t op_exp([](float a) { return std::exp(a); }, "exp"); + static blt::gp::operation_t op_log([](float a) { return a == 0.0f ? 0.0f : std::log(a); }, "log"); + static blt::gp::operation_t op_mod( + [](float a, float b) { return static_cast(b) <= 0 ? 0.0f : static_cast(static_cast(a) % static_cast(b)); }, "mod"); + static blt::gp::operation_t op_b_mod( + [](float a, float b) { + return blt::mem::type_cast(b) <= 0 ? 0.0f : blt::mem::type_cast( + blt::mem::type_cast(a) % blt::mem::type_cast(b)); + }, "b_mod"); + static blt::gp::operation_t op_v_mod( + [](float a, float b) { + return blt::mem::type_cast(b) <= 0 ? 0.0f : static_cast(blt::mem::type_cast(a) % blt::mem::type_cast(b)); + }, + "v_mod"); + static blt::gp::operation_t bitwise_and([](float a, float b) { + return blt::mem::type_cast(blt::mem::type_cast(a) & blt::mem::type_cast(b)); + }, "b_and"); + static blt::gp::operation_t bitwise_or([](float a, float b) { + return blt::mem::type_cast(blt::mem::type_cast(a) | blt::mem::type_cast(b)); + }, "b_or"); + static blt::gp::operation_t bitwise_xor([](float a, float b) { + return blt::mem::type_cast(blt::mem::type_cast(a) ^ blt::mem::type_cast(b)); + }, "b_xor"); - BLT_DEBUG("Setup Types and Operators"); - type_system.register_type(); + static blt::gp::operation_t bw_raw_and([](float a, float b) { + return static_cast(blt::mem::type_cast(a) & blt::mem::type_cast(b)); + }, "raw_and"); + static blt::gp::operation_t bw_raw_or([](float a, float b) { + return static_cast(blt::mem::type_cast(a) | blt::mem::type_cast(b)); + }, "raw_or"); + static blt::gp::operation_t bw_raw_xor([](float a, float b) { + return static_cast(blt::mem::type_cast(a) ^ blt::mem::type_cast(b)); + }, "raw_xor"); + + static blt::gp::operation_t value_and([](float a, float b) { + return static_cast(a) & static_cast(b); + }, "v_and"); + static blt::gp::operation_t value_or([](float a, float b) { + return static_cast(a) | static_cast(b); + }, "v_or"); + static blt::gp::operation_t value_xor([](float a, float b) { + return static_cast(a) ^ static_cast(b); + }, "v_xor"); + + static blt::gp::operation_t lit([&program]() { + return program.get_random().get_float(0.0f, 1.0f); + }, "lit"); + static blt::gp::operation_t random([&program]() { + return program.get_random().get_float(0.0f, 1.0f); + }, "random"); + static blt::gp::operation_t perlin([](float x, float y, float z, float scale) { + if (scale == 0) + scale = 1; + return stb_perlin_noise3(x / scale, y / scale, z / scale, 0, 0, 0); + }, "perlin"); + static blt::gp::operation_t perlin_terminal([](const context& context) { + return stb_perlin_noise3(context.x / IMAGE_SIZE, context.y / IMAGE_SIZE, 0.23423, 0, 0, 0); + }, "perlin_term"); + static blt::gp::operation_t perlin_bumpy([](float x, float y, float z) { + return stb_perlin_noise3(x / 128.0f, y / 128.0f, z / 128.0f, 0, 0, 0); + }, "perlin_bump"); + static blt::gp::operation_t op_x([](const context& context) { + return context.x; + }, "x"); + static blt::gp::operation_t op_y([](const context& context) { + return context.y; + }, "y"); blt::gp::operator_builder builder{type_system}; + builder.add_operator(perlin); + builder.add_operator(perlin_bumpy); + builder.add_operator(perlin_terminal); + builder.add_operator(add); builder.add_operator(sub); builder.add_operator(mul); @@ -105,53 +198,232 @@ int main() builder.add_operator(op_cos); builder.add_operator(op_exp); builder.add_operator(op_log); +// builder.add_operator(op_mod); +// builder.add_operator(op_b_mod); + builder.add_operator(op_v_mod); +// builder.add_operator(bitwise_and); +// builder.add_operator(bitwise_or); +// builder.add_operator(bitwise_xor); +// builder.add_operator(value_and); +// builder.add_operator(value_or); +// builder.add_operator(value_xor); + builder.add_operator(bw_raw_and); + builder.add_operator(bw_raw_or); + builder.add_operator(bw_raw_xor); builder.add_operator(lit, true); + builder.add_operator(random); builder.add_operator(op_x); + builder.add_operator(op_y); program.set_operations(builder.build()); - - BLT_DEBUG("Generate Initial Population"); - program.generate_population(type_system.get_type().id(), fitness_function); - +} + +inline context get_ctx(blt::size_t i) +{ + context ctx{}; + ctx.y = std::floor(static_cast(i) / static_cast(IMAGE_SIZE)); + ctx.x = static_cast(i) - (ctx.y * IMAGE_SIZE); +// ctx.x = static_cast(i / IMAGE_SIZE); +// ctx.y = static_cast(i % IMAGE_SIZE); +// std::cout << ctx.x << " " << ctx.y << std::endl; + return ctx; +} + +constexpr auto create_fitness_function(fitness_data_t& fitness_data, blt::size_t channel) +{ + return [&fitness_data, channel](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t in) { + auto& v = fitness_data[in]; + for (blt::size_t i = 0; i < DATA_SIZE; i++) + { + context ctx = get_ctx(i); + v.image_data[i] = static_cast(current_tree.get_evaluation_value(&ctx) * 255); + + auto dist = static_cast(v.image_data[i]) - static_cast(base_data.image_data[i * CHANNELS + channel]); + + fitness.raw_fitness += std::sqrt(dist * dist); + } + BLT_TRACE("Hello1"); + cv::Mat img(IMAGE_SIZE, IMAGE_SIZE, CV_8UC3, v.image_data.data()); + BLT_TRACE("Hello2"); + cv::Mat img_hsv; + BLT_TRACE("Hello3"); + cv::cvtColor(img, img_hsv, cv::COLOR_RGB2HSV); + BLT_TRACE("Hello4"); + cv::Mat hist; + BLT_TRACE("Hello5"); + cv::calcHist(&img_hsv, 1, channels, cv::Mat(), hist, 2, histSize, ranges, true, false); + BLT_TRACE("Hello6"); + cv::normalize(hist, hist, 0, 1, cv::NORM_MINMAX, -1, cv::Mat()); + BLT_TRACE("Hello7"); + + auto comp = cv::compareHist(base_image_hist, hist, cv::HISTCMP_CORREL); + + fitness.standardized_fitness = fitness.raw_fitness / IMAGE_SIZE; + fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness)) * comp; + }; +} + +constexpr auto fitness_function_red = create_fitness_function(fitness_red, 0); + +constexpr auto fitness_function_green = create_fitness_function(fitness_green, 1); + +constexpr auto fitness_function_blue = create_fitness_function(fitness_blue, 2); + +void evaluate_program(blt::gp::gp_program& program) +{ BLT_DEBUG("Begin Generation Loop"); while (!program.should_terminate()) { BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation()); - BLT_START_INTERVAL("Symbolic Regression", "Gen"); + BLT_START_INTERVAL("Image Test", "Gen"); program.create_next_generation(blt::gp::select_tournament_t{}, blt::gp::select_tournament_t{}, blt::gp::select_tournament_t{}); - BLT_END_INTERVAL("Symbolic Regression", "Gen"); + BLT_END_INTERVAL("Image Test", "Gen"); BLT_TRACE("Move to next generation"); - BLT_START_INTERVAL("Symbolic Regression", "Fitness"); + BLT_START_INTERVAL("Image Test", "Fitness"); program.next_generation(); BLT_TRACE("Evaluate Fitness"); program.evaluate_fitness(); - BLT_END_INTERVAL("Symbolic Regression", "Fitness"); + BLT_END_INTERVAL("Image Test", "Fitness"); BLT_TRACE("----------------------------------------------"); std::cout << std::endl; } - - BLT_END_INTERVAL("Symbolic Regression", "Main"); - - auto best = program.get_best_individuals<3>(); - - BLT_INFO("Best approximations:"); - for (auto& i_ref : best) - { - auto& i = i_ref.get(); - BLT_DEBUG("Fitness: %lf, stand: %lf, raw: %lf", i.fitness.adjusted_fitness, i.fitness.standardized_fitness, i.fitness.raw_fitness); - i.tree.print(program, std::cout); - std::cout << "\n"; - } +} + +void print_stats(blt::gp::gp_program& program) +{ auto& stats = program.get_population_stats(); BLT_INFO("Stats:"); BLT_INFO("Average fitness: %lf", stats.average_fitness.load()); BLT_INFO("Best fitness: %lf", stats.best_fitness.load()); BLT_INFO("Worst fitness: %lf", stats.worst_fitness.load()); BLT_INFO("Overall fitness: %lf", stats.overall_fitness.load()); +} + +void write_tree_large(int image_size, blt::size_t index, blt::size_t best_red, blt::size_t best_blue, blt::size_t best_green) +{ + auto value = std::unique_ptr(new blt::u8[image_size * image_size * CHANNELS]); + + BLT_TRACE("Writing large image of index %ld", index); + auto& red = program_red.get_current_pop().get_individuals()[best_red].tree; + auto& green = program_green.get_current_pop().get_individuals()[best_green].tree; + auto& blue = program_blue.get_current_pop().get_individuals()[best_blue].tree; + + for (blt::size_t i = 0; i < static_cast(image_size) * image_size; i++) + { + auto ctx = get_ctx(i); + value.get()[i * CHANNELS] = static_cast(red.get_evaluation_value(&ctx) * 255); + value.get()[i * CHANNELS + 1] = static_cast(green.get_evaluation_value(&ctx) * 255); + value.get()[i * CHANNELS + 2] = static_cast(blue.get_evaluation_value(&ctx) * 255); + } + + stbi_write_png(("best_image_large_" + std::to_string(index) + ".png").c_str(), image_size, image_size, CHANNELS, value.get(), 0); +} + +void write_tree(blt::size_t index, blt::size_t best_red, blt::size_t best_blue, blt::size_t best_green) +{ + BLT_TRACE("Writing tree of index %ld", index); + std::cout << "Red: "; + program_red.get_current_pop().get_individuals()[best_red].tree.print(program_red, std::cout); + std::cout << "Green: "; + program_green.get_current_pop().get_individuals()[best_green].tree.print(program_green, std::cout); + std::cout << "Blue: "; + program_blue.get_current_pop().get_individuals()[best_blue].tree.print(program_blue, std::cout); + + for (blt::size_t i = 0; i < DATA_SIZE; i++) + { + found_data.image_data[i * CHANNELS] = fitness_red[best_red].image_data[i]; + found_data.image_data[i * CHANNELS + 1] = fitness_green[best_green].image_data[i]; + found_data.image_data[i * CHANNELS + 2] = fitness_blue[best_blue].image_data[i]; + } + + found_data.save("best_image_" + std::to_string(index) + ".png"); +} + +void write_results() +{ + constexpr static blt::size_t best_count = 5; + auto best_red = program_red.get_best_indexes(); + auto best_green = program_green.get_best_indexes(); + auto best_blue = program_blue.get_best_indexes(); + + for (blt::size_t i = 0; i < best_count; i++) + { + write_tree(i, best_red[i], best_green[i], best_blue[i]); + write_tree_large(512, i, best_red[i], best_green[i], best_blue[i]); + } + + print_stats(program_red); + print_stats(program_green); + print_stats(program_blue); +} + +template +auto convert_args(context& ctx, Arg&& arg) +{ + if constexpr (std::is_same_v>, context>) + { + return ctx; + } else + { + return std::forward(arg); + } +} + +template +void make_operator_image(T op, Args... args) +{ + auto value = std::unique_ptr(new blt::u8[IMAGE_SIZE * IMAGE_SIZE * CHANNELS]); + + for (blt::size_t i = 0; i < IMAGE_SIZE * IMAGE_SIZE; i++) + { + auto ctx = get_ctx(i); + value.get()[i * CHANNELS] = static_cast(op(convert_args(ctx, args)...) * 255); + value.get()[i * CHANNELS + 1] = static_cast(op(convert_args(ctx, args)...) * 255); + value.get()[i * CHANNELS + 2] = static_cast(op(convert_args(ctx, args)...) * 255); + } + + stbi_write_png((blt::type_string + ".png").c_str(), IMAGE_SIZE, IMAGE_SIZE, CHANNELS, value.get(), 0); +} + +int main() +{ + BLT_INFO("Starting BLT-GP Image Test"); + BLT_INFO("Using Seed: %ld", SEED); + BLT_START_INTERVAL("Image Test", "Main"); + BLT_DEBUG("Setup Base Image"); + base_data.load("../Rolex_De_Grande_-_Joo.png"); + + cv::Mat base_image_mat{IMAGE_SIZE, IMAGE_SIZE, CV_8UC3, base_data.image_data.data()}; + cv::cvtColor(base_image_mat, base_image_hsv, cv::COLOR_RGB2HSV); + + cv::calcHist( &base_image_hsv, 1, channels, cv::Mat(), base_image_hist, 2, histSize, ranges, true, false ); + cv::normalize( base_image_hist, base_image_hist, 0, 1, cv::NORM_MINMAX, -1, cv::Mat() ); + + BLT_DEBUG("Setup Types and Operators"); + type_system.register_type(); + + create_program(program_red); + create_program(program_green); + create_program(program_blue); + + BLT_DEBUG("Generate Initial Population"); + program_red.generate_population(type_system.get_type().id(), fitness_function_red); + program_green.generate_population(type_system.get_type().id(), fitness_function_green); + program_blue.generate_population(type_system.get_type().id(), fitness_function_blue); + + evaluate_program(program_red); + evaluate_program(program_green); + evaluate_program(program_blue); + + BLT_END_INTERVAL("Image Test", "Main"); + + write_results(); + base_data.save("input.png"); + // TODO: make stats helper - BLT_PRINT_PROFILE("Symbolic Regression", blt::PRINT_CYCLES | blt::PRINT_THREAD | blt::PRINT_WALL); + BLT_PRINT_PROFILE("Image Test", blt::PRINT_CYCLES | blt::PRINT_THREAD | blt::PRINT_WALL); return 0; } \ No newline at end of file