public GridNode(CameraCalibration _CameraCalibration) { if (_CameraCalibration == null) { throw new Exception("Invalid camera calibration to build grid node!"); } m_CameraCalibration = _CameraCalibration; // Build normalized EV infos Convert2EV(m_CameraCalibration.m_CameraShotInfos.m_ISOSpeed, m_CameraCalibration.m_CameraShotInfos.m_ShutterSpeed, m_CameraCalibration.m_CameraShotInfos.m_Aperture, out m_EV_ISOSpeed, out m_EV_ShutterSpeed, out m_EV_Aperture); }
private void buttonLoadCalibration_Click( object sender, EventArgs e ) { string OldFileName = GetRegKey( "LastCalibrationFilename", m_ApplicationPath ); openFileDialogCalibration.InitialDirectory = System.IO.Path.GetDirectoryName( OldFileName ); openFileDialogCalibration.FileName = System.IO.Path.GetFileName( OldFileName ); if ( openFileDialogCalibration.ShowDialog( this ) != DialogResult.OK ) return; SetRegKey( "LastCalibrationFilename", openFileDialogCalibration.FileName ); try { CameraCalibration NewCalibration = new CameraCalibration(); NewCalibration.Load( new System.IO.FileInfo( openFileDialogCalibration.FileName ) ); m_Calibration = NewCalibration; UpdateUIFromCalibration(); } catch ( Exception _e ) { MessageBox( "An error occurred while loading calibration file:\r\n\r\n", _e ); } }
private void StartCalibrationPicking( CameraCalibration.Probe _Probe ) { if ( m_BitmapXYZ == null ) { // No image loaded you moron! MessageBox( "Can't start calibration as no image is currently loaded!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation ); return; } outputPanel.StartCalibrationTargetPicking( ( ImageUtility.float2 _Center, float _Radius ) => { IntegrateLuminance( _Probe, _Center, _Radius ); } ); }
/// <summary> /// Integrates luminance for the provided probe by sampling luminances in the provided disc /// </summary> /// <param name="_Probe"></param> /// <param name="_Center"></param> /// <param name="_Radius"></param> private void IntegrateLuminance( CameraCalibration.Probe _Probe, ImageUtility.float2 _Center, float _Radius ) { ImageUtility.float3 MinXYZ, MaxXYZ; float BlackValues, SaturatedValues; ImageUtility.float3 AverageXYZ = IntegrateLuminance( _Center.x, _Center.y, _Radius, out MinXYZ, out MaxXYZ, out BlackValues, out SaturatedValues ); bool DisableProbe = false; if ( BlackValues > BLACK_VALUES_TOLERANCE && MessageBox( "This probe has more than 5% luminance values that are too dark, it's advised you don't use it to calibrate the camera as its values will not be useful.\r\n" + "\r\nDo you wish to disable this probe?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning ) == DialogResult.Yes ) { DisableProbe = true; } else if ( SaturatedValues > SATURATED_VALUES_TOLERANCE && MessageBox( "This probe has more than 5% luminance values that are saturated, it's advised you don't use it to calibrate the camera as its values will not be useful.\r\n" + "\r\nDo you wish to disable this probe?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning ) == DialogResult.Yes ) { DisableProbe = true; } if ( DisableProbe ) { // Disable probe _Probe.m_IsAvailable = false; _Probe.m_LuminanceMeasured = 0.0f; UpdateUIFromCalibration(); return; } _Probe.m_LuminanceMeasured = checkBoxCalibrationUseAverageLuminance.Checked ? AverageXYZ.y : MaxXYZ.y; // We now have valid measurement disc infos _Probe.m_MeasurementDiscIsAvailable = true; _Probe.m_MeasurementCenterX = _Center.x; _Probe.m_MeasurementCenterY = _Center.y; _Probe.m_MeasurementRadius = _Radius; CommitImageToCurrentCalibration(); // We used the current image as reference for this calibration so commit its data }
public GridNode( CameraCalibration _CameraCalibration ) { if ( _CameraCalibration == null ) throw new Exception( "Invalid camera calibration to build grid node!" ); m_CameraCalibration = _CameraCalibration; // Build normalized EV infos Convert2EV( m_CameraCalibration.m_CameraShotInfos.m_ISOSpeed, m_CameraCalibration.m_CameraShotInfos.m_ShutterSpeed, m_CameraCalibration.m_CameraShotInfos.m_Aperture, out m_EV_ISOSpeed, out m_EV_ShutterSpeed, out m_EV_Aperture ); }
/// <summary> /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos /// </summary> /// <param name="_ISOSpeed"></param> /// <param name="_ShutterSpeed"></param> /// <param name="_Aperture"></param> public void PrepareCalibrationFor( float _ISOSpeed, float _ShutterSpeed, float _Aperture ) { if ( m_RootNode == null ) throw new Exception( "Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?" ); if ( IsPreparedFor( _ISOSpeed, _ShutterSpeed, _Aperture ) ) return; // Already prepared! ////////////////////////////////////////////////////////////////////////// // Find the 8 nodes encompassing our values // I'm making the delicate assumption that, although the starting node is chosen on the // condition its EV values are strictly inferior to the target we're looking for, all // neighbor nodes should satisfy the condition they're properly placed. // // This is true for the direct neighbors +X, +Y, +Z that are immediately above target values // but for example, neighbor (+X +Y) may have a very bad aperture value (Z) that may be // above the target aperture... // // Let's hope the user won't provide too fancy calibrations... // (anyway, interpolants are clamped in [0,1] so there's no risk of overshooting) // ImageUtility.float3 EV; GridNode.Convert2EV( _ISOSpeed, _ShutterSpeed, _Aperture, out EV.x, out EV.y, out EV.z ); // Find the start node GridNode StartNode = FindStartNode( EV.x, EV.y, EV.z ); m_InterpolationStartNode = StartNode; // Build the 8 grid nodes from it GridNode[,,] Grid = new GridNode[2,2,2]; Grid[0,0,0] = StartNode; Grid[1,0,0] = StartNode.m_Neighbors[0][1] != null ? StartNode.m_Neighbors[0][1] : StartNode; // +X Grid[0,1,0] = StartNode.m_Neighbors[1][1] != null ? StartNode.m_Neighbors[1][1] : StartNode; // +Y Grid[0,0,1] = StartNode.m_Neighbors[2][1] != null ? StartNode.m_Neighbors[2][1] : StartNode; // +Z Grid[1,1,0] = Grid[1,0,0].m_Neighbors[1][1] != null ? Grid[1,0,0].m_Neighbors[1][1] : Grid[1,0,0]; // +X +Y Grid[0,1,1] = Grid[0,1,0].m_Neighbors[2][1] != null ? Grid[0,1,0].m_Neighbors[2][1] : Grid[0,1,0]; // +Y +Z Grid[1,0,1] = Grid[0,0,1].m_Neighbors[0][1] != null ? Grid[0,0,1].m_Neighbors[0][1] : Grid[0,0,1]; // +X +Z Grid[1,1,1] = Grid[1,1,0].m_Neighbors[2][1] != null ? Grid[1,1,0].m_Neighbors[2][1] : Grid[1,1,0]; // +X +Y +Z ////////////////////////////////////////////////////////////////////////// // Create the successive interpolants for trilinear interpolation // // Assume we interpolate on X first (ISO speed), so we need 4 distinct values ImageUtility.float4 tX = new ImageUtility.float4( Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,0,0].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,0,0].m_EV_ISOSpeed - Grid[0,0,0].m_EV_ISOSpeed) ) ), // Y=0 Z=0 Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,1,0].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,1,0].m_EV_ISOSpeed - Grid[0,1,0].m_EV_ISOSpeed) ) ), // Y=1 Z=0 Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,0,1].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,0,1].m_EV_ISOSpeed - Grid[0,0,1].m_EV_ISOSpeed) ) ), // Y=0 Z=1 Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,1,1].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,1,1].m_EV_ISOSpeed - Grid[0,1,1].m_EV_ISOSpeed) ) ) // Y=1 Z=1 ); ImageUtility.float4 rX = new ImageUtility.float4( 1.0f - tX.x, 1.0f - tX.y, 1.0f - tX.z, 1.0f - tX.w ); // Compute the 4 interpolated shutter speeds & apertures ImageUtility.float4 ShutterSpeedsX = new ImageUtility.float4( rX.x * Grid[0,0,0].m_EV_ShutterSpeed + tX.x * Grid[1,0,0].m_EV_ShutterSpeed, // Y=0 Z=0 rX.y * Grid[0,1,0].m_EV_ShutterSpeed + tX.y * Grid[1,1,0].m_EV_ShutterSpeed, // Y=1 Z=0 rX.z * Grid[0,0,1].m_EV_ShutterSpeed + tX.z * Grid[1,0,1].m_EV_ShutterSpeed, // Y=0 Z=1 rX.w * Grid[0,1,1].m_EV_ShutterSpeed + tX.w * Grid[1,1,1].m_EV_ShutterSpeed // Y=1 Z=1 ); ImageUtility.float4 AperturesX = new ImageUtility.float4( rX.x * Grid[0,0,0].m_EV_Aperture + tX.x * Grid[1,0,0].m_EV_Aperture, // Y=0 Z=0 rX.y * Grid[0,1,0].m_EV_Aperture + tX.y * Grid[1,1,0].m_EV_Aperture, // Y=1 Z=0 rX.z * Grid[0,0,1].m_EV_Aperture + tX.z * Grid[1,0,1].m_EV_Aperture, // Y=0 Z=1 rX.w * Grid[0,1,1].m_EV_Aperture + tX.w * Grid[1,1,1].m_EV_Aperture // Y=1 Z=1 ); // Next we interpolate on Y (Shutter speed), so we need 2 distinct values ImageUtility.float2 tY = new ImageUtility.float2( Math.Max( 0.0f, Math.Min( 1.0f, (EV.y - ShutterSpeedsX.x) / Math.Max( 1e-6f, ShutterSpeedsX.y - ShutterSpeedsX.x) ) ), // Z=0 Math.Max( 0.0f, Math.Min( 1.0f, (EV.y - ShutterSpeedsX.z) / Math.Max( 1e-6f, ShutterSpeedsX.w - ShutterSpeedsX.z) ) ) // Z=1 ); ImageUtility.float2 rY = new ImageUtility.float2( 1.0f - tY.x, 1.0f - tY.y ); // Compute the 2 apertures ImageUtility.float2 AperturesY = new ImageUtility.float2( rY.x * AperturesX.x + tY.x * AperturesX.y, rY.y * AperturesX.z + tY.y * AperturesX.w ); // Finally, we interpolate on Z (Aperture), we need only 1 single value float tZ = Math.Max( 0.0f, Math.Min( 1.0f, (EV.z - AperturesY.x) / Math.Max( 1e-6f, AperturesY.y - AperturesY.x) ) ); float rZ = 1.0f - tZ; ////////////////////////////////////////////////////////////////////////// // Create the special camera calibration that is the result of the interpolation of the 8 nearest ones in the grid m_InterpolatedCalibration = new CameraCalibration(); m_InterpolatedCalibration.m_CameraShotInfos.m_ISOSpeed = _ISOSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_ShutterSpeed = _ShutterSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_Aperture = _Aperture; for ( int ProbeIndex=0; ProbeIndex < REQUIRED_PROBES_COUNT; ProbeIndex++ ) { CameraCalibration.Probe TargetProbe = m_InterpolatedCalibration.m_Reflectances[ProbeIndex]; float L000 = Grid[0,0,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L100 = Grid[1,0,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L010 = Grid[0,1,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L110 = Grid[1,1,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L001 = Grid[0,0,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L101 = Grid[1,0,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L011 = Grid[0,1,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L111 = Grid[1,1,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; // Interpolate on X (ISO speed) float L00 = rX.x * L000 + tX.x * L100; float L10 = rX.y * L010 + tX.y * L110; float L01 = rX.z * L001 + tX.z * L101; float L11 = rX.w * L011 + tX.w * L111; // Interpolate on Y (shutter speed) float L0 = rY.x * L00 + tY.x * L10; float L1 = rY.y * L01 + tY.y * L11; // Interpolate on Z (aperture) float L = rZ * L0 + tZ * L1; TargetProbe.m_IsAvailable = true; TargetProbe.m_LuminanceMeasured = L; } // Fill missing values m_InterpolatedCalibration.UpdateAllLuminances(); // Reset white reflectance reference because it was set for another setup WhiteReflectanceReference = new ImageUtility.float3( 0, 0, -1 ); }
/// <summary> /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos /// </summary> /// <param name="_ISOSpeed"></param> /// <param name="_ShutterSpeed"></param> /// <param name="_Aperture"></param> public void PrepareCalibrationFor(float _ISOSpeed, float _ShutterSpeed, float _Aperture) { if (m_RootNode == null) { throw new Exception("Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?"); } if (IsPreparedFor(_ISOSpeed, _ShutterSpeed, _Aperture)) { return; // Already prepared! } ////////////////////////////////////////////////////////////////////////// // Find the 8 nodes encompassing our values // I'm making the delicate assumption that, although the starting node is chosen on the // condition its EV values are strictly inferior to the target we're looking for, all // neighbor nodes should satisfy the condition they're properly placed. // // This is true for the direct neighbors +X, +Y, +Z that are immediately above target values // but for example, neighbor (+X +Y) may have a very bad aperture value (Z) that may be // above the target aperture... // // Let's hope the user won't provide too fancy calibrations... // (anyway, interpolants are clamped in [0,1] so there's no risk of overshooting) // ImageUtility.float3 EV; GridNode.Convert2EV(_ISOSpeed, _ShutterSpeed, _Aperture, out EV.x, out EV.y, out EV.z); // Find the start node GridNode StartNode = FindStartNode(EV.x, EV.y, EV.z); m_InterpolationStartNode = StartNode; // Build the 8 grid nodes from it GridNode[,,] Grid = new GridNode[2, 2, 2]; Grid[0, 0, 0] = StartNode; Grid[1, 0, 0] = StartNode.m_Neighbors[0][1] != null ? StartNode.m_Neighbors[0][1] : StartNode; // +X Grid[0, 1, 0] = StartNode.m_Neighbors[1][1] != null ? StartNode.m_Neighbors[1][1] : StartNode; // +Y Grid[0, 0, 1] = StartNode.m_Neighbors[2][1] != null ? StartNode.m_Neighbors[2][1] : StartNode; // +Z Grid[1, 1, 0] = Grid[1, 0, 0].m_Neighbors[1][1] != null ? Grid[1, 0, 0].m_Neighbors[1][1] : Grid[1, 0, 0]; // +X +Y Grid[0, 1, 1] = Grid[0, 1, 0].m_Neighbors[2][1] != null ? Grid[0, 1, 0].m_Neighbors[2][1] : Grid[0, 1, 0]; // +Y +Z Grid[1, 0, 1] = Grid[0, 0, 1].m_Neighbors[0][1] != null ? Grid[0, 0, 1].m_Neighbors[0][1] : Grid[0, 0, 1]; // +X +Z Grid[1, 1, 1] = Grid[1, 1, 0].m_Neighbors[2][1] != null ? Grid[1, 1, 0].m_Neighbors[2][1] : Grid[1, 1, 0]; // +X +Y +Z ////////////////////////////////////////////////////////////////////////// // Create the successive interpolants for trilinear interpolation // // Assume we interpolate on X first (ISO speed), so we need 4 distinct values ImageUtility.float4 tX = new ImageUtility.float4( Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 0, 0].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 0, 0].m_EV_ISOSpeed - Grid[0, 0, 0].m_EV_ISOSpeed))), // Y=0 Z=0 Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 1, 0].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 1, 0].m_EV_ISOSpeed - Grid[0, 1, 0].m_EV_ISOSpeed))), // Y=1 Z=0 Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 0, 1].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 0, 1].m_EV_ISOSpeed - Grid[0, 0, 1].m_EV_ISOSpeed))), // Y=0 Z=1 Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 1, 1].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 1, 1].m_EV_ISOSpeed - Grid[0, 1, 1].m_EV_ISOSpeed))) // Y=1 Z=1 ); ImageUtility.float4 rX = new ImageUtility.float4(1.0f - tX.x, 1.0f - tX.y, 1.0f - tX.z, 1.0f - tX.w); // Compute the 4 interpolated shutter speeds & apertures ImageUtility.float4 ShutterSpeedsX = new ImageUtility.float4( rX.x * Grid[0, 0, 0].m_EV_ShutterSpeed + tX.x * Grid[1, 0, 0].m_EV_ShutterSpeed, // Y=0 Z=0 rX.y * Grid[0, 1, 0].m_EV_ShutterSpeed + tX.y * Grid[1, 1, 0].m_EV_ShutterSpeed, // Y=1 Z=0 rX.z * Grid[0, 0, 1].m_EV_ShutterSpeed + tX.z * Grid[1, 0, 1].m_EV_ShutterSpeed, // Y=0 Z=1 rX.w * Grid[0, 1, 1].m_EV_ShutterSpeed + tX.w * Grid[1, 1, 1].m_EV_ShutterSpeed // Y=1 Z=1 ); ImageUtility.float4 AperturesX = new ImageUtility.float4( rX.x * Grid[0, 0, 0].m_EV_Aperture + tX.x * Grid[1, 0, 0].m_EV_Aperture, // Y=0 Z=0 rX.y * Grid[0, 1, 0].m_EV_Aperture + tX.y * Grid[1, 1, 0].m_EV_Aperture, // Y=1 Z=0 rX.z * Grid[0, 0, 1].m_EV_Aperture + tX.z * Grid[1, 0, 1].m_EV_Aperture, // Y=0 Z=1 rX.w * Grid[0, 1, 1].m_EV_Aperture + tX.w * Grid[1, 1, 1].m_EV_Aperture // Y=1 Z=1 ); // Next we interpolate on Y (Shutter speed), so we need 2 distinct values ImageUtility.float2 tY = new ImageUtility.float2( Math.Max(0.0f, Math.Min(1.0f, (EV.y - ShutterSpeedsX.x) / Math.Max(1e-6f, ShutterSpeedsX.y - ShutterSpeedsX.x))), // Z=0 Math.Max(0.0f, Math.Min(1.0f, (EV.y - ShutterSpeedsX.z) / Math.Max(1e-6f, ShutterSpeedsX.w - ShutterSpeedsX.z))) // Z=1 ); ImageUtility.float2 rY = new ImageUtility.float2(1.0f - tY.x, 1.0f - tY.y); // Compute the 2 apertures ImageUtility.float2 AperturesY = new ImageUtility.float2( rY.x * AperturesX.x + tY.x * AperturesX.y, rY.y * AperturesX.z + tY.y * AperturesX.w ); // Finally, we interpolate on Z (Aperture), we need only 1 single value float tZ = Math.Max(0.0f, Math.Min(1.0f, (EV.z - AperturesY.x) / Math.Max(1e-6f, AperturesY.y - AperturesY.x))); float rZ = 1.0f - tZ; ////////////////////////////////////////////////////////////////////////// // Create the special camera calibration that is the result of the interpolation of the 8 nearest ones in the grid m_InterpolatedCalibration = new CameraCalibration(); m_InterpolatedCalibration.m_CameraShotInfos.m_ISOSpeed = _ISOSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_ShutterSpeed = _ShutterSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_Aperture = _Aperture; for (int ProbeIndex = 0; ProbeIndex < REQUIRED_PROBES_COUNT; ProbeIndex++) { CameraCalibration.Probe TargetProbe = m_InterpolatedCalibration.m_Reflectances[ProbeIndex]; float L000 = Grid[0, 0, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L100 = Grid[1, 0, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L010 = Grid[0, 1, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L110 = Grid[1, 1, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L001 = Grid[0, 0, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L101 = Grid[1, 0, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L011 = Grid[0, 1, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L111 = Grid[1, 1, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; // Interpolate on X (ISO speed) float L00 = rX.x * L000 + tX.x * L100; float L10 = rX.y * L010 + tX.y * L110; float L01 = rX.z * L001 + tX.z * L101; float L11 = rX.w * L011 + tX.w * L111; // Interpolate on Y (shutter speed) float L0 = rY.x * L00 + tY.x * L10; float L1 = rY.y * L01 + tY.y * L11; // Interpolate on Z (aperture) float L = rZ * L0 + tZ * L1; TargetProbe.m_IsAvailable = true; TargetProbe.m_LuminanceMeasured = L; } // Fill missing values m_InterpolatedCalibration.UpdateAllLuminances(); // Reset white reflectance reference because it was set for another setup WhiteReflectanceReference = new ImageUtility.float3(0, 0, -1); }