diff --git a/gopro_8:7/unfish_gopro_8:7.glsl b/gopro_8:7/unfish_gopro_8:7.glsl index 36805ba..eb6099e 100644 --- a/gopro_8:7/unfish_gopro_8:7.glsl +++ b/gopro_8:7/unfish_gopro_8:7.glsl @@ -1,14 +1,15 @@ // RozK // Fisheye removal for GoPro 11+, 8:7 ratio, without hypersmooth -// Adapted from https://github.com/duducosmos/defisheye -// itself based on http://www.fmwconcepts.com/imagemagick/defisheye/index.php #extension GL_ARB_texture_rectangle: enable // TODO: investigate // precision highp float; +// #define debug_borders + // uniforms + uniform sampler2DRect myTextureY; uniform sampler2DRect myTextureU; uniform sampler2DRect myTextureV; @@ -16,25 +17,47 @@ uniform vec2 myResolution; uniform float pts; // parameters -const float input_fov = 156.0; -const float output_fov = 124.45; -const vec2 pixel_scale = vec2(0.652485, 1.0); + +const vec2 sensor_dimensions = vec2(5.949440, 5.205760); +const float fisheye_focal_length = 2.92; +const float rectilinear_focal_length = 2.102263; const int subsampling = 4; -// subsampling constants -const float substep = 1.0 / float(subsampling); -const float substart = substep * 0.5 - 0.5; -const float subscale = 1.0 / float(subsampling * subsampling); +// constants + +const float subsampling_step = 1.0 / float(subsampling); +const float subsampling_start = subsampling_step * 0.5 - 0.5; +const float subsampling_denominator = 1.0 / float(subsampling * subsampling); // variables -vec2 center; -float diameter; -float input_len; -float inv_output_len; -vec4 unfish(const in vec2 coord) { - float len = max(0.001, length(coord)); - vec2 y_coord = center + coord * ((input_len / len) * atan(len * inv_output_len)); +vec2 texture_center; +vec2 texture_to_sensor; +vec2 sensor_to_texture; + +void initialize() { + texture_center = myResolution * 0.5; + texture_to_sensor = (sensor_dimensions / myResolution); + sensor_to_texture = (myResolution / sensor_dimensions); +} + +vec2 unfish_coord(const in vec2 coord) { + float rectilinear_distance = length(coord); + float rectilinear_angle = atan(rectilinear_distance / rectilinear_focal_length); + float fisheye_distance = rectilinear_angle * fisheye_focal_length; + return coord * (fisheye_distance / rectilinear_distance); +} + +vec4 unfish_pixel(const in vec2 coord) { + vec2 unfished = unfish_coord((coord - texture_center) * texture_to_sensor); + vec2 y_coord = texture_center + unfished * sensor_to_texture; + +#ifdef debug_borders + if (y_coord.x < 0.0 || y_coord.y < 0.0 || y_coord.x > myResolution.x || y_coord.y > myResolution.y) { + return vec4(1.0, 1.0, 1.0, 1.0); + } +#endif + vec2 uv_coord = y_coord * 0.5; return vec4( texture2DRect(myTextureY, y_coord).r, @@ -45,21 +68,18 @@ vec4 unfish(const in vec2 coord) { } void main() { - center = myResolution * 0.5; - diameter = length(myResolution); - input_len = diameter / radians(input_fov); - inv_output_len = (2.0 * tan(radians(output_fov * 0.5))) / diameter; + initialize(); - vec2 coord = gl_TexCoord[0].xy - center; + vec2 coord = gl_TexCoord[0].xy; vec4 pixel = vec4(0.0, 0.0, 0.0, 0.0); - float x, y = substart; - for (int column = 0; column < subsampling; column++, y += substep) { - x = substart; - for (int row = 0; row < subsampling; row++, x += substep) { - pixel += unfish((coord + vec2(x, y)) * pixel_scale); + float x, y = subsampling_start; + for (int column = 0; column < subsampling; column++, y += subsampling_step) { + x = subsampling_start; + for (int row = 0; row < subsampling; row++, x += subsampling_step) { + pixel += unfish_pixel(coord + vec2(x, y)); } } - gl_FragColor = pixel * subscale; + gl_FragColor = pixel * subsampling_denominator; } diff --git a/gopro_8:7/unfish_gopro_8:7.py b/gopro_8:7/unfish_gopro_8:7.py new file mode 100644 index 0000000..40599d8 --- /dev/null +++ b/gopro_8:7/unfish_gopro_8:7.py @@ -0,0 +1,86 @@ +# RozK + +import math + +print("\n--- sensor size (millimeters) ---\n") + +# https://www.sony-semicon.com/files/62/pdf/p-13_IMX677-AAPH5-J_Flyer.pdf + +sensor_total_array_width = 5700 # p +sensor_total_array_height = 5160 # p +sensor_total_array_diagonal = math.hypot(sensor_total_array_width, sensor_total_array_height) + +sensor_pixel_size = 1.12 * 0.001 # mm + +sensor_total_width = sensor_total_array_width * sensor_pixel_size +sensor_total_height = sensor_total_array_height * sensor_pixel_size +sensor_total_diagonal = math.hypot(sensor_total_width, sensor_total_height) + +# sensor_total_diagonal = 8.35 # mm +# sensor_total_width = sensor_total_diagonal * (sensor_total_array_width / sensor_total_array_diagonal) +# sensor_total_height = sensor_total_diagonal * (sensor_total_array_height / sensor_total_array_diagonal) + +print("sensor total: width = %.6f, height = %.6f, diagonal = %.6f" % ( + sensor_total_width, + sensor_total_height, + sensor_total_diagonal)) + +sensor_active_array_width = 5599 # p +sensor_active_array_height = 4927 # p +sensor_active_array_diagonal = math.hypot(sensor_active_array_width, sensor_active_array_height) + +sensor_active_width = (sensor_active_array_width / sensor_total_array_width) * sensor_total_width +sensor_active_height = (sensor_active_array_height / sensor_total_array_height) * sensor_total_height +sensor_active_diagonal = (sensor_active_array_diagonal / sensor_total_array_diagonal) * sensor_total_diagonal + +print("sensor active: width = %.6f, height = %.6f, diagonal = %.6f" % ( + sensor_active_width, + sensor_active_height, + sensor_active_diagonal)) + +# https://community.gopro.com/s/article/HERO11-Black-Video-Settings-And-Resolutions + +gopro_array_width = 5312 # p +gopro_array_height = 4648 # p +gopro_array_diagonal = math.hypot(gopro_array_width, gopro_array_height) + +gopro_sensor_width = (gopro_array_width / sensor_total_array_width) * sensor_total_width +gopro_sensor_height = (gopro_array_height / sensor_total_array_height) * sensor_total_height +gopro_sensor_diagonal = (gopro_array_diagonal / sensor_total_array_diagonal) * sensor_total_diagonal + +print("gopro active: width = %.6f, height = %.6f, diagonal = %.6f" % ( + gopro_sensor_width, + gopro_sensor_height, + gopro_sensor_diagonal)) + +# https://thinglabs.io/gopro-focal-length-guide + +gopro_focal_length = 2.92 # mm + +print("\n--- fisheye field of view (degrees) ---\n") + +# https://en.wikipedia.org/wiki/Fisheye_lens + +equidistant_angle = lambda length: math.degrees(length / gopro_focal_length) + +gopro_fov_width = 2.0 * equidistant_angle(gopro_sensor_width * 0.5) +gopro_fov_height = 2.0 * equidistant_angle(gopro_sensor_height * 0.5) +gopro_fov_diagonal = 2.0 * equidistant_angle(gopro_sensor_diagonal * 0.5) + +print("gopro fov: width = %.6f, height = %.6f, diagonal = %.6f" % ( + gopro_fov_width, + gopro_fov_height, + gopro_fov_diagonal)) + +print("\n--- rectilinear focal length (mm) ---\n") + +rectilinear_focal_length = lambda length: length * math.tan(math.radians(90.0 - equidistant_angle(length))) + +rectilinear_focal_length_width = rectilinear_focal_length(gopro_sensor_width * 0.5) +rectilinear_focal_length_height = rectilinear_focal_length(gopro_sensor_height * 0.5) +rectilinear_focal_length_diagonal = rectilinear_focal_length(gopro_sensor_diagonal * 0.5) + +print("rectilinear focal length: width = %.6f, height = %.6f, diagonal = %.6f" % ( + rectilinear_focal_length_width, + rectilinear_focal_length_height, + rectilinear_focal_length_diagonal))