X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=flow.cpp;h=17618816be51bc321b6ffa41356a145cd767e653;hb=1622572018b35982441b53b93c78cf6610fb1799;hp=15161988eb77ddab0c9eaf070385763f40038584;hpb=c214d6919f05020b0695bccb8c948e6229c51375;p=nageru diff --git a/flow.cpp b/flow.cpp index 1516198..1761881 100644 --- a/flow.cpp +++ b/flow.cpp @@ -1,8 +1,5 @@ #define NO_SDL_GLEXT 1 -#define WIDTH 1280 -#define HEIGHT 720 - #include #include @@ -33,6 +30,11 @@ constexpr unsigned coarsest_level = 5; constexpr unsigned finest_level = 1; constexpr unsigned patch_size_pixels = 12; +// Weighting constants for the different parts of the variational refinement. +// These don't correspond 1:1 to the values given in the DIS paper, +// since we have different normalizations and ranges in some cases. +float vr_gamma = 10.0f, vr_delta = 5.0f, vr_alpha = 10.0f; + // Some global OpenGL objects. GLuint nearest_sampler, linear_sampler, smoothness_sampler; GLuint vertex_vbo; @@ -115,26 +117,41 @@ GLuint compile_shader(const string &shader_src, GLenum type) return obj; } - -GLuint load_texture(const char *filename, unsigned width, unsigned height) +GLuint load_texture(const char *filename, unsigned *width_ret, unsigned *height_ret) { - FILE *fp = fopen(filename, "rb"); - if (fp == nullptr) { - perror(filename); + SDL_Surface *surf = IMG_Load(filename); + if (surf == nullptr) { + fprintf(stderr, "IMG_Load(%s): %s\n", filename, IMG_GetError()); exit(1); } - unique_ptr pix(new uint8_t[width * height]); - if (fread(pix.get(), width * height, 1, fp) != 1) { - fprintf(stderr, "Short read from %s\n", filename); + + // For whatever reason, SDL doesn't support converting to YUV surfaces + // nor grayscale, so we'll do it (slowly) ourselves. + SDL_Surface *rgb_surf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_RGBA8888, /*flags=*/0); + if (rgb_surf == nullptr) { + fprintf(stderr, "SDL_ConvertSurfaceFormat(%s): %s\n", filename, SDL_GetError()); exit(1); } - fclose(fp); - // Convert to bottom-left origin. - for (unsigned y = 0; y < height / 2; ++y) { + SDL_FreeSurface(surf); + + unsigned width = rgb_surf->w, height = rgb_surf->h; + const uint8_t *sptr = (uint8_t *)rgb_surf->pixels; + unique_ptr pix(new uint8_t[width * height]); + + // Extract the Y component, and convert to bottom-left origin. + for (unsigned y = 0; y < height; ++y) { unsigned y2 = height - 1 - y; - swap_ranges(&pix[y * width], &pix[y * width + width], &pix[y2 * width]); + for (unsigned x = 0; x < width; ++x) { + uint8_t r = sptr[(y2 * width + x) * 4 + 3]; + uint8_t g = sptr[(y2 * width + x) * 4 + 2]; + uint8_t b = sptr[(y2 * width + x) * 4 + 1]; + + // Rec. 709. + pix[y * width + x] = lrintf(r * 0.2126f + g * 0.7152f + b * 0.0722f); + } } + SDL_FreeSurface(rgb_surf); int levels = 1; for (int w = width, h = height; w > 1 || h > 1; ) { @@ -149,6 +166,9 @@ GLuint load_texture(const char *filename, unsigned width, unsigned height) glTextureSubImage2D(tex, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, pix.get()); glGenerateTextureMipmap(tex); + *width_ret = width; + *height_ret = height; + return tex; } @@ -556,6 +576,7 @@ private: GLuint smoothness_vao; GLuint uniform_flow_tex, uniform_diff_flow_tex; + GLuint uniform_alpha; }; ComputeSmoothness::ComputeSmoothness() @@ -575,6 +596,7 @@ ComputeSmoothness::ComputeSmoothness() uniform_flow_tex = glGetUniformLocation(smoothness_program, "flow_tex"); uniform_diff_flow_tex = glGetUniformLocation(smoothness_program, "diff_flow_tex"); + uniform_alpha = glGetUniformLocation(smoothness_program, "alpha"); } void ComputeSmoothness::exec(GLuint flow_tex, GLuint diff_flow_tex, GLuint smoothness_x_tex, GLuint smoothness_y_tex, int level_width, int level_height) @@ -583,6 +605,7 @@ void ComputeSmoothness::exec(GLuint flow_tex, GLuint diff_flow_tex, GLuint smoot bind_sampler(smoothness_program, uniform_flow_tex, 0, flow_tex, nearest_sampler); bind_sampler(smoothness_program, uniform_diff_flow_tex, 1, diff_flow_tex, nearest_sampler); + glProgramUniform1f(smoothness_program, uniform_alpha, vr_alpha); GLuint smoothness_fbo; // TODO: cleanup glCreateFramebuffers(1, &smoothness_fbo); @@ -631,6 +654,7 @@ private: GLuint uniform_diff_flow_tex, uniform_base_flow_tex; GLuint uniform_beta_0_tex; GLuint uniform_smoothness_x_tex, uniform_smoothness_y_tex; + GLuint uniform_gamma, uniform_delta; }; SetupEquations::SetupEquations() @@ -655,6 +679,8 @@ SetupEquations::SetupEquations() uniform_beta_0_tex = glGetUniformLocation(equations_program, "beta_0_tex"); uniform_smoothness_x_tex = glGetUniformLocation(equations_program, "smoothness_x_tex"); uniform_smoothness_y_tex = glGetUniformLocation(equations_program, "smoothness_y_tex"); + uniform_gamma = glGetUniformLocation(equations_program, "gamma"); + uniform_delta = glGetUniformLocation(equations_program, "delta"); } void SetupEquations::exec(GLuint I_x_y_tex, GLuint I_t_tex, GLuint diff_flow_tex, GLuint base_flow_tex, GLuint beta_0_tex, GLuint smoothness_x_tex, GLuint smoothness_y_tex, GLuint equation_tex, int level_width, int level_height) @@ -668,6 +694,8 @@ void SetupEquations::exec(GLuint I_x_y_tex, GLuint I_t_tex, GLuint diff_flow_tex bind_sampler(equations_program, uniform_beta_0_tex, 4, beta_0_tex, nearest_sampler); bind_sampler(equations_program, uniform_smoothness_x_tex, 5, smoothness_x_tex, smoothness_sampler); bind_sampler(equations_program, uniform_smoothness_y_tex, 6, smoothness_y_tex, smoothness_sampler); + glProgramUniform1f(equations_program, uniform_delta, vr_delta); + glProgramUniform1f(equations_program, uniform_gamma, vr_gamma); GLuint equations_fbo; // TODO: cleanup glCreateFramebuffers(1, &equations_fbo); @@ -680,11 +708,8 @@ void SetupEquations::exec(GLuint I_x_y_tex, GLuint I_t_tex, GLuint diff_flow_tex glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } -// Calculate the smoothness constraints between neighboring pixels; -// s_x(x,y) stores smoothness between pixel (x,y) and (x+1,y), -// and s_y(x,y) stores between (x,y) and (x,y+1). We'll sample with -// border color (0,0) later, so that there's zero diffusion out of -// the border. +// Actually solve the equation sets made by SetupEquations, by means of +// successive over-relaxation (SOR). // // See variational_refinement.txt for more information. class SOR { @@ -832,6 +857,61 @@ pair GPUTimers::begin_timer(const string &name, int level) return timer.query; } +// Take a copy of the flow, bilinearly interpolated and scaled up. +class ResizeFlow { +public: + ResizeFlow(); + void exec(GLuint in_tex, GLuint out_tex, int input_width, int input_height, int output_width, int output_height); + +private: + GLuint resize_flow_vs_obj; + GLuint resize_flow_fs_obj; + GLuint resize_flow_program; + GLuint resize_flow_vao; + + GLuint uniform_flow_tex; + GLuint uniform_scale_factor; +}; + +ResizeFlow::ResizeFlow() +{ + resize_flow_vs_obj = compile_shader(read_file("vs.vert"), GL_VERTEX_SHADER); + resize_flow_fs_obj = compile_shader(read_file("resize_flow.frag"), GL_FRAGMENT_SHADER); + resize_flow_program = link_program(resize_flow_vs_obj, resize_flow_fs_obj); + + // Set up the VAO containing all the required position/texcoord data. + glCreateVertexArrays(1, &resize_flow_vao); + glBindVertexArray(resize_flow_vao); + glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo); + + GLint position_attrib = glGetAttribLocation(resize_flow_program, "position"); + glEnableVertexArrayAttrib(resize_flow_vao, position_attrib); + glVertexAttribPointer(position_attrib, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); + + uniform_flow_tex = glGetUniformLocation(resize_flow_program, "flow_tex"); + uniform_scale_factor = glGetUniformLocation(resize_flow_program, "scale_factor"); +} + +void ResizeFlow::exec(GLuint flow_tex, GLuint out_tex, int input_width, int input_height, int output_width, int output_height) +{ + glUseProgram(resize_flow_program); + + bind_sampler(resize_flow_program, uniform_flow_tex, 0, flow_tex, nearest_sampler); + + glProgramUniform2f(resize_flow_program, uniform_scale_factor, float(output_width) / input_width, float(output_height) / input_height); + + GLuint resize_flow_fbo; // TODO: cleanup + glCreateFramebuffers(1, &resize_flow_fbo); + glNamedFramebufferTexture(resize_flow_fbo, GL_COLOR_ATTACHMENT0, out_tex, 0); + + glViewport(0, 0, output_width, output_height); + glDisable(GL_BLEND); + glBindVertexArray(resize_flow_vao); + glBindFramebuffer(GL_FRAMEBUFFER, resize_flow_fbo); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + void GPUTimers::print() { for (const Timer &timer : timers) { @@ -843,7 +923,7 @@ void GPUTimers::print() for (int i = 0; i < timer.level * 2; ++i) { fprintf(stderr, " "); } - fprintf(stderr, "%-30s %4.1f ms\n", timer.name.c_str(), (time_end - time_start) / 1e6); + fprintf(stderr, "%-30s %4.1f ms\n", timer.name.c_str(), GLint64(time_end - time_start) / 1e6); } } @@ -883,7 +963,7 @@ private: bool ended = false; }; -int main(void) +int main(int argc, char **argv) { if (SDL_Init(SDL_INIT_EVERYTHING) == -1) { fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); @@ -907,8 +987,15 @@ int main(void) assert(context != nullptr); // Load pictures. - GLuint tex0 = load_texture("test1499.pgm", WIDTH, HEIGHT); - GLuint tex1 = load_texture("test1500.pgm", WIDTH, HEIGHT); + unsigned width1, height1, width2, height2; + GLuint tex0 = load_texture(argc >= 2 ? argv[1] : "test1499.png", &width1, &height1); + GLuint tex1 = load_texture(argc >= 3 ? argv[2] : "test1500.png", &width2, &height2); + + if (width1 != width2 || height1 != height2) { + fprintf(stderr, "Image dimensions don't match (%dx%d versus %dx%d)\n", + width1, height1, width2, height2); + exit(1); + } // Make some samplers. glCreateSamplers(1, &nearest_sampler); @@ -947,6 +1034,7 @@ int main(void) GLuint initial_flow_tex; glCreateTextures(GL_TEXTURE_2D, 1, &initial_flow_tex); glTextureStorage2D(initial_flow_tex, 1, GL_RG16F, 1, 1); + glClearTexImage(initial_flow_tex, 0, GL_RG, GL_FLOAT, nullptr); int prev_level_width = 1, prev_level_height = 1; GLuint prev_level_flow_tex = initial_flow_tex; @@ -960,6 +1048,7 @@ int main(void) SetupEquations setup_equations; SOR sor; AddBaseFlow add_base_flow; + ResizeFlow resize_flow; GLuint query; glGenQueries(1, &query); @@ -973,8 +1062,8 @@ int main(void) snprintf(timer_name, sizeof(timer_name), "Level %d", level); ScopedTimer level_timer(timer_name, &total_timer); - int level_width = WIDTH >> level; - int level_height = HEIGHT >> level; + int level_width = width1 >> level; + int level_height = height1 >> level; float patch_spacing_pixels = patch_size_pixels * (1.0f - patch_overlap_ratio); int width_patches = 1 + lrintf((level_width - patch_size_pixels) / patch_spacing_pixels); int height_patches = 1 + lrintf((level_height - patch_size_pixels) / patch_spacing_pixels); @@ -1111,7 +1200,7 @@ int main(void) // Add the differential flow found by the variational refinement to the base flow, // giving the final flow estimate for this level. // The output is in diff_flow_tex; we don't need to make a new texture. - // You can comment out this prat if you wish to test disabling of the variational refinement. + // You can comment out this part if you wish to test disabling of the variational refinement. { ScopedTimer timer("Add differential flow", &varref_timer); add_base_flow.exec(base_flow_tex, du_dv_tex, level_width, level_height); @@ -1125,22 +1214,30 @@ int main(void) timers.print(); - int level_width = WIDTH >> finest_level; - int level_height = HEIGHT >> finest_level; - unique_ptr dense_flow(new float[level_width * level_height * 2]); - glGetTextureImage(prev_level_flow_tex, 0, GL_RG, GL_FLOAT, level_width * level_height * 2 * sizeof(float), dense_flow.get()); + // Scale up the flow to the final size (if needed). + GLuint final_tex; + if (finest_level == 0) { + final_tex = prev_level_flow_tex; + } else { + glCreateTextures(GL_TEXTURE_2D, 1, &final_tex); + glTextureStorage2D(final_tex, 1, GL_RG16F, width1, height1); + resize_flow.exec(prev_level_flow_tex, final_tex, prev_level_width, prev_level_height, width1, height1); + } + + unique_ptr dense_flow(new float[width1 * height1 * 2]); + glGetTextureImage(final_tex, 0, GL_RG, GL_FLOAT, width1 * height1 * 2 * sizeof(float), dense_flow.get()); FILE *fp = fopen("flow.ppm", "wb"); FILE *flowfp = fopen("flow.flo", "wb"); - fprintf(fp, "P6\n%d %d\n255\n", level_width, level_height); + fprintf(fp, "P6\n%d %d\n255\n", width1, height1); fprintf(flowfp, "FEIH"); - fwrite(&level_width, 4, 1, flowfp); - fwrite(&level_height, 4, 1, flowfp); - for (unsigned y = 0; y < unsigned(level_height); ++y) { - int yy = level_height - y - 1; - for (unsigned x = 0; x < unsigned(level_width); ++x) { - float du = dense_flow[(yy * level_width + x) * 2 + 0]; - float dv = dense_flow[(yy * level_width + x) * 2 + 1]; + fwrite(&width1, 4, 1, flowfp); + fwrite(&height1, 4, 1, flowfp); + for (unsigned y = 0; y < unsigned(height1); ++y) { + int yy = height1 - y - 1; + for (unsigned x = 0; x < unsigned(width1); ++x) { + float du = dense_flow[(yy * width1 + x) * 2 + 0]; + float dv = dense_flow[(yy * width1 + x) * 2 + 1]; dv = -dv;