// since we have different normalizations and ranges in some cases.
float vr_gamma = 10.0f, vr_delta = 5.0f, vr_alpha = 10.0f;
+bool enable_timing = true;
+bool enable_variational_refinement = true; // Just for debugging.
+
// Some global OpenGL objects.
// TODO: These should really be part of DISComputeFlow.
GLuint nearest_sampler, linear_sampler, smoothness_sampler;
pair<GLuint, GLuint> GPUTimers::begin_timer(const string &name, int level)
{
+ if (!enable_timing) {
+ return make_pair(0, 0);
+ }
+
GLuint queries[2];
glGenQueries(2, queries);
glQueryCounter(queries[0], GL_TIMESTAMP);
void end()
{
- if (!ended) {
+ if (enable_timing && !ended) {
glQueryCounter(query.second, GL_TIMESTAMP);
ended = true;
}
class DISComputeFlow {
public:
DISComputeFlow(int width, int height);
+
+ // Returns a texture that must be released with release_texture()
+ // after use.
GLuint exec(GLuint tex0, GLuint tex1);
+ void release_texture(GLuint tex);
private:
int width, height;
SOR sor;
AddBaseFlow add_base_flow;
ResizeFlow resize_flow;
+
+ struct Texture {
+ GLuint tex_num;
+ GLenum format;
+ GLuint width, height;
+ bool in_use = false;
+ };
+ vector<Texture> textures;
+
+ GLuint get_texture(GLenum format, GLuint width, GLuint height);
};
DISComputeFlow::DISComputeFlow(int width, int height)
GLuint DISComputeFlow::exec(GLuint tex0, GLuint tex1)
{
+ for (const Texture &tex : textures) {
+ assert(!tex.in_use);
+ }
+
int prev_level_width = 1, prev_level_height = 1;
GLuint prev_level_flow_tex = initial_flow_tex;
// Create a new texture; we could be fancy and render use a multi-level
// texture, but meh.
- GLuint grad0_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &grad0_tex);
- glTextureStorage2D(grad0_tex, 1, GL_RG16F, level_width, level_height);
+ GLuint grad0_tex = get_texture(GL_RG16F, level_width, level_height);
// Find the derivative.
{
// level (sampled bilinearly; no fancy tricks) as a guide, then search from there.
// Create an output flow texture.
- GLuint flow_out_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &flow_out_tex);
- glTextureStorage2D(flow_out_tex, 1, GL_RGB16F, width_patches, height_patches);
+ GLuint flow_out_tex = get_texture(GL_RGB16F, width_patches, height_patches);
// And draw.
{
ScopedTimer timer("Motion search", &level_timer);
motion_search.exec(tex0_view, tex1_view, grad0_tex, prev_level_flow_tex, flow_out_tex, level_width, level_height, prev_level_width, prev_level_height, width_patches, height_patches);
}
+ release_texture(grad0_tex);
// Densification.
// Set up an output texture (initially zero).
- GLuint dense_flow_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &dense_flow_tex);
- glTextureStorage2D(dense_flow_tex, 1, GL_RGB16F, level_width, level_height);
+ GLuint dense_flow_tex = get_texture(GL_RGB16F, level_width, level_height);
glClearTexImage(dense_flow_tex, 0, GL_RGB, GL_FLOAT, nullptr);
// And draw.
ScopedTimer timer("Densification", &level_timer);
densify.exec(tex0_view, tex1_view, flow_out_tex, dense_flow_tex, level_width, level_height, width_patches, height_patches);
}
+ release_texture(flow_out_tex);
// Everything below here in the loop belongs to variational refinement.
ScopedTimer varref_timer("Variational refinement", &level_timer);
// in pixels, not 0..1 normalized OpenGL texture coordinates.
// This is because variational refinement depends so heavily on derivatives,
// which are measured in intensity levels per pixel.
- GLuint I_tex, I_t_tex, base_flow_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &I_tex);
- glCreateTextures(GL_TEXTURE_2D, 1, &I_t_tex);
- glCreateTextures(GL_TEXTURE_2D, 1, &base_flow_tex);
- glTextureStorage2D(I_tex, 1, GL_R16F, level_width, level_height);
- glTextureStorage2D(I_t_tex, 1, GL_R16F, level_width, level_height);
- glTextureStorage2D(base_flow_tex, 1, GL_RG16F, level_width, level_height);
+ GLuint I_tex = get_texture(GL_R16F, level_width, level_height);
+ GLuint I_t_tex = get_texture(GL_R16F, level_width, level_height);
+ GLuint base_flow_tex = get_texture(GL_RG16F, level_width, level_height);
{
ScopedTimer timer("Prewarping", &varref_timer);
prewarp.exec(tex0_view, tex1_view, dense_flow_tex, I_tex, I_t_tex, base_flow_tex, level_width, level_height);
}
+ release_texture(dense_flow_tex);
// Calculate I_x and I_y. We're only calculating first derivatives;
// the others will be taken on-the-fly in order to sample from fewer
// textures overall, since sampling from the L1 cache is cheap.
// (TODO: Verify that this is indeed faster than making separate
// double-derivative textures.)
- GLuint I_x_y_tex, beta_0_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &I_x_y_tex);
- glCreateTextures(GL_TEXTURE_2D, 1, &beta_0_tex);
- glTextureStorage2D(I_x_y_tex, 1, GL_RG16F, level_width, level_height);
- glTextureStorage2D(beta_0_tex, 1, GL_R16F, level_width, level_height);
+ GLuint I_x_y_tex = get_texture(GL_RG16F, level_width, level_height);
+ GLuint beta_0_tex = get_texture(GL_R16F, level_width, level_height);
{
ScopedTimer timer("First derivatives", &varref_timer);
derivatives.exec(I_tex, I_x_y_tex, beta_0_tex, level_width, level_height);
}
+ release_texture(I_tex);
// We need somewhere to store du and dv (the flow increment, relative
// to the non-refined base flow u0 and v0). It starts at zero.
- GLuint du_dv_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &du_dv_tex);
- glTextureStorage2D(du_dv_tex, 1, GL_RG16F, level_width, level_height);
+ GLuint du_dv_tex = get_texture(GL_RG16F, level_width, level_height);
glClearTexImage(du_dv_tex, 0, GL_RG, GL_FLOAT, nullptr);
// And for smoothness.
- GLuint smoothness_x_tex, smoothness_y_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &smoothness_x_tex);
- glCreateTextures(GL_TEXTURE_2D, 1, &smoothness_y_tex);
- glTextureStorage2D(smoothness_x_tex, 1, GL_R16F, level_width, level_height);
- glTextureStorage2D(smoothness_y_tex, 1, GL_R16F, level_width, level_height);
+ GLuint smoothness_x_tex = get_texture(GL_R16F, level_width, level_height);
+ GLuint smoothness_y_tex = get_texture(GL_R16F, level_width, level_height);
// And finally for the equation set. See SetupEquations for
// the storage format.
- GLuint equation_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &equation_tex);
- glTextureStorage2D(equation_tex, 1, GL_RGBA32UI, level_width, level_height);
+ GLuint equation_tex = get_texture(GL_RGBA32UI, level_width, level_height);
for (int outer_idx = 0; outer_idx < level + 1; ++outer_idx) {
// Calculate the smoothness terms between the neighboring pixels,
}
}
+ release_texture(I_t_tex);
+ release_texture(I_x_y_tex);
+ release_texture(beta_0_tex);
+ release_texture(smoothness_x_tex);
+ release_texture(smoothness_y_tex);
+ release_texture(equation_tex);
+
// 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 part if you wish to test disabling of the variational refinement.
- {
+ //
+ // Disabling this doesn't save any time (although we could easily make it so that
+ // it is more efficient), but it helps debug the motion search.
+ if (enable_variational_refinement) {
ScopedTimer timer("Add differential flow", &varref_timer);
add_base_flow.exec(base_flow_tex, du_dv_tex, level_width, level_height);
}
+ release_texture(du_dv_tex);
+ if (prev_level_flow_tex != initial_flow_tex) {
+ release_texture(prev_level_flow_tex);
+ }
prev_level_flow_tex = base_flow_tex;
prev_level_width = level_width;
prev_level_height = level_height;
if (finest_level == 0) {
return prev_level_flow_tex;
} else {
- GLuint final_tex;
- glCreateTextures(GL_TEXTURE_2D, 1, &final_tex);
- glTextureStorage2D(final_tex, 1, GL_RG16F, width, height);
+ GLuint final_tex = get_texture(GL_RG16F, width, height);
resize_flow.exec(prev_level_flow_tex, final_tex, prev_level_width, prev_level_height, width, height);
+ release_texture(prev_level_flow_tex);
return final_tex;
}
}
+GLuint DISComputeFlow::get_texture(GLenum format, GLuint width, GLuint height)
+{
+ for (Texture &tex : textures) {
+ if (!tex.in_use && tex.format == format &&
+ tex.width == width && tex.height == height) {
+ tex.in_use = true;
+ return tex.tex_num;
+ }
+ }
+
+ Texture tex;
+ glCreateTextures(GL_TEXTURE_2D, 1, &tex.tex_num);
+ glTextureStorage2D(tex.tex_num, 1, format, width, height);
+ tex.format = format;
+ tex.width = width;
+ tex.height = height;
+ tex.in_use = true;
+ textures.push_back(tex);
+ return tex.tex_num;
+}
+
+void DISComputeFlow::release_texture(GLuint tex_num)
+{
+ for (Texture &tex : textures) {
+ if (tex.tex_num == tex_num) {
+ assert(tex.in_use);
+ tex.in_use = false;
+ return;
+ }
+ }
+ assert(false);
+}
+
+// OpenGL uses a bottom-left coordinate system, .flo files use a top-left coordinate system.
+void flip_coordinate_system(float *dense_flow, unsigned width, unsigned height)
+{
+ for (unsigned i = 0; i < width * height; ++i) {
+ dense_flow[i * 2 + 1] = -dense_flow[i * 2 + 1];
+ }
+}
+
+void write_flow(const char *filename, const float *dense_flow, unsigned width, unsigned height)
+{
+ FILE *flowfp = fopen(filename, "wb");
+ fprintf(flowfp, "FEIH");
+ fwrite(&width, 4, 1, flowfp);
+ fwrite(&height, 4, 1, flowfp);
+ for (unsigned y = 0; y < height; ++y) {
+ int yy = height - y - 1;
+ fwrite(&dense_flow[yy * width * 2], width * 2 * sizeof(float), 1, flowfp);
+ }
+ fclose(flowfp);
+}
+
+void write_ppm(const char *filename, const float *dense_flow, unsigned width, unsigned height)
+{
+ FILE *fp = fopen(filename, "wb");
+ fprintf(fp, "P6\n%d %d\n255\n", width, height);
+ for (unsigned y = 0; y < unsigned(height); ++y) {
+ int yy = height - y - 1;
+ for (unsigned x = 0; x < unsigned(width); ++x) {
+ float du = dense_flow[(yy * width + x) * 2 + 0];
+ float dv = dense_flow[(yy * width + x) * 2 + 1];
+
+ uint8_t r, g, b;
+ flow2rgb(du, dv, &r, &g, &b);
+ putc(r, fp);
+ putc(g, fp);
+ putc(b, fp);
+ }
+ }
+ fclose(fp);
+}
+
int main(int argc, char **argv)
{
static const option long_options[] = {
{ "alpha", required_argument, 0, 'a' },
{ "delta", required_argument, 0, 'd' },
- { "gamma", required_argument, 0, 'g' }
+ { "gamma", required_argument, 0, 'g' },
+ { "disable-timing", no_argument, 0, 1000 },
+ { "ignore-variational-refinement", no_argument, 0, 1001 } // Still calculates it, just doesn't apply it.
};
for ( ;; ) {
case 'g':
vr_gamma = atof(optarg);
break;
+ case 1000:
+ enable_timing = false;
+ break;
+ case 1001:
+ enable_variational_refinement = false;
+ break;
default:
fprintf(stderr, "Unknown option '%s'\n", argv[option_index]);
exit(1);
SDL_GLContext context = SDL_GL_CreateContext(window);
assert(context != nullptr);
+ const char *filename0 = argc >= (optind + 1) ? argv[optind] : "test1499.png";
+ const char *filename1 = argc >= (optind + 2) ? argv[optind + 1] : "test1500.png";
+ const char *flow_filename = argc >= (optind + 3) ? argv[optind + 2] : "flow.flo";
+ fprintf(stderr, "%s %s -> %s\n", filename0, filename1, flow_filename);
+
// Load pictures.
unsigned width1, height1, width2, height2;
- GLuint tex0 = load_texture(argc >= (optind + 1) ? argv[optind] : "test1499.png", &width1, &height1);
- GLuint tex1 = load_texture(argc >= (optind + 2) ? argv[optind + 1] : "test1500.png", &width2, &height2);
+ GLuint tex0 = load_texture(filename0, &width1, &height1);
+ GLuint tex1 = load_texture(filename1, &width2, &height2);
if (width1 != width2 || height1 != height2) {
fprintf(stderr, "Image dimensions don't match (%dx%d versus %dx%d)\n",
unique_ptr<float[]> 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", width1, height1);
- fprintf(flowfp, "FEIH");
- 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];
+ compute_flow.release_texture(final_tex);
- dv = -dv;
+ flip_coordinate_system(dense_flow.get(), width1, height1);
+ write_flow(flow_filename, dense_flow.get(), width1, height1);
+ write_ppm("flow.ppm", dense_flow.get(), width1, height1);
- fwrite(&du, 4, 1, flowfp);
- fwrite(&dv, 4, 1, flowfp);
+ dense_flow.reset();
- uint8_t r, g, b;
- flow2rgb(du, dv, &r, &g, &b);
- putc(r, fp);
- putc(g, fp);
- putc(b, fp);
+ // See if there are more flows on the command line (ie., more than three arguments),
+ // and if so, process them.
+ int num_flows = (argc - optind) / 3;
+ for (int i = 1; i < num_flows; ++i) {
+ const char *filename0 = argv[optind + i * 3 + 0];
+ const char *filename1 = argv[optind + i * 3 + 1];
+ const char *flow_filename = argv[optind + i * 3 + 2];
+ fprintf(stderr, "%s %s -> %s\n", filename0, filename1, flow_filename);
+
+ GLuint width, height;
+ GLuint tex0 = load_texture(filename0, &width, &height);
+ if (width != width1 || height != height1) {
+ fprintf(stderr, "%s: Image dimensions don't match (%dx%d versus %dx%d)\n",
+ filename0, width, height, width1, height1);
+ exit(1);
+ }
+
+ GLuint tex1 = load_texture(filename1, &width, &height);
+ if (width != width1 || height != height1) {
+ fprintf(stderr, "%s: Image dimensions don't match (%dx%d versus %dx%d)\n",
+ filename1, width, height, width1, height1);
+ exit(1);
}
+
+ GLuint final_tex = compute_flow.exec(tex0, tex1);
+
+ unique_ptr<float[]> dense_flow(new float[width * height * 2]);
+ glGetTextureImage(final_tex, 0, GL_RG, GL_FLOAT, width * height * 2 * sizeof(float), dense_flow.get());
+
+ compute_flow.release_texture(final_tex);
+
+ flip_coordinate_system(dense_flow.get(), width, height);
+ write_flow(flow_filename, dense_flow.get(), width, height);
}
- fclose(fp);
- fclose(flowfp);
fprintf(stderr, "err = %d\n", glGetError());
}