TuttleOFX
1
|
00001 #include "NLMDenoiserDefinitions.hpp" 00002 #include "NLMDenoiserPlugin.hpp" 00003 #include "imageUtils/noiseAnalysis.hpp" 00004 00005 #include <tuttle/common/utils/global.hpp> 00006 #include <tuttle/common/math/rectOp.hpp> 00007 #include <tuttle/plugin/ImageGilProcessor.hpp> 00008 #include <tuttle/plugin/IProgress.hpp> 00009 #include <tuttle/plugin/exceptions.hpp> 00010 #include <terry/globals.hpp> 00011 #include <terry/basic_colors.hpp> 00012 #include <terry/channel.hpp> 00013 00014 #include <ofxsImageEffect.h> 00015 #include <ofxsMultiThread.h> 00016 00017 #include <boost/gil/gil_all.hpp> 00018 #include <boost/scoped_ptr.hpp> 00019 00020 #include <cassert> 00021 #include <cmath> 00022 #include <vector> 00023 #include <sstream> 00024 00025 00026 namespace tuttle { 00027 namespace plugin { 00028 namespace nlmDenoiser { 00029 00030 namespace bgil = boost::gil; 00031 00032 template<class View> 00033 NLMDenoiserProcess<View>::NLMDenoiserProcess( NLMDenoiserPlugin & instance ) 00034 : ImageGilProcessor<View>( instance, eImageOrientationIndependant ) 00035 , _plugin( instance ) 00036 { 00037 _paramRedStrength = instance.fetchDoubleParam( kParamRedStrength ); 00038 _paramGreenStrength = instance.fetchDoubleParam( kParamGreenStrength ); 00039 _paramBlueStrength = instance.fetchDoubleParam( kParamBlueStrength ); 00040 00041 _paramRedGrainSize = instance.fetchDoubleParam( kParamRedGrainSize ); 00042 _paramGreenGrainSize = instance.fetchDoubleParam( kParamGreenGrainSize ); 00043 _paramBlueGrainSize = instance.fetchDoubleParam( kParamBlueGrainSize ); 00044 00045 _paramPatchRadius = instance.fetchIntParam( kParamPatchRadius ); 00046 _paramRegionRadius = instance.fetchIntParam( kParamRegionRadius ); 00047 _paramDepth = instance.fetchIntParam( kParamDepth ); 00048 _paramPreBlurring = instance.fetchDoubleParam( kParamPreBlurring ); 00049 00050 _paramOptimized = instance.fetchBooleanParam( kParamOptimization ); 00051 } 00052 00053 template<class View> 00054 NLMDenoiserProcess<View>::~NLMDenoiserProcess() 00055 { 00056 } 00057 00058 template<class View> 00059 void NLMDenoiserProcess<View>::addFrame( const OfxRectI & dBounds, const int dstBitDepth, const int dstComponents, const double time, const int z ) 00060 { 00061 // Fetch main input image 00062 TUTTLE_TLOG( TUTTLE_INFO, "NLMDenoiserProcess<View>::addFrame time:" << time ); 00063 OFX::Image *img = _plugin._clipSrc->fetchImage( time ); 00064 if( !img ) 00065 BOOST_THROW_EXCEPTION( exception::ImageNotReady() ); 00066 _srcImgs.push_back( img ); 00067 OfxRectI bounds = img->getBounds(); 00068 const int w = bounds.x2 - bounds.x1; 00069 const int h = bounds.y2 - bounds.y1; 00070 00071 memcpy( &_upScaledBounds, &bounds, sizeof (OfxRectI ) ); 00072 00073 // Build views 00074 _srcViews.push_back( 00075 bgil::interleaved_view( w, h, (Pixel *) img->getPixelData(), img->getRowDistanceBytes() ) ); 00076 00077 // Make sure bit depths are same 00078 OFX::EBitDepth srcBitDepth = img->getPixelDepth(); 00079 OFX::EPixelComponent srcComponents = img->getPixelComponents(); 00080 00081 // See if they have the same bit depths 00082 if( srcBitDepth != dstBitDepth || srcComponents != dstComponents ) 00083 { 00084 BOOST_THROW_EXCEPTION( exception::BitDepthMismatch() ); 00085 } 00086 } 00087 00088 template<class View> 00089 void NLMDenoiserProcess<View>::setup( const OFX::RenderArguments &args ) 00090 { 00091 const int depth = _paramDepth->getValue(); 00092 00093 _srcViews.clear(); 00094 _srcImgs.clear(); 00095 00096 this->_dst.reset( _plugin._clipDst->fetchImage( args.time ) ); 00097 // Fetch output image 00098 if( !this->_dst.get() ) 00099 BOOST_THROW_EXCEPTION( exception::ImageNotReady() ); 00100 const OfxRectI dBounds = this->_dst->getBounds(); 00101 00102 const OFX::EBitDepth dstBitDepth = this->_dst->getPixelDepth(); 00103 const OFX::EPixelComponent dstComponents = this->_dst->getPixelComponents(); 00104 00105 this->_dstView = bgil::interleaved_view( dBounds.x2 - dBounds.x1, dBounds.y2 - dBounds.y1, 00106 ( Pixel * ) this->_dst->getPixelData(), 00107 this->_dst->getRowDistanceBytes() ); 00108 00109 // Get render frame range 00110 const OfxRangeD clipFullRange = _plugin._clipSrc->getFrameRange(); 00111 OfxRangeD requestedRange; 00112 requestedRange.min = args.time - depth; 00113 requestedRange.max = args.time + depth; 00114 OfxRangeD realRange; 00115 realRange.min = std::max( requestedRange.min, clipFullRange.min ); 00116 realRange.max = std::min( requestedRange.max, clipFullRange.max ); 00117 00118 int i = 0; 00119 addFrame( dBounds, dstBitDepth, dstComponents, (int) ( args.time ), i++ ); 00120 00121 for( OfxTime t = args.time - 1; t >= realRange.min; --t ) 00122 { 00123 TUTTLE_TLOG_VAR2( TUTTLE_INFO, args.time, t ); 00124 addFrame( dBounds, dstBitDepth, dstComponents, t, i++ ); 00125 } 00126 00127 for( OfxTime t = args.time + 1; t <= realRange.max; ++t ) 00128 { 00129 TUTTLE_TLOG_VAR2( TUTTLE_INFO, args.time, t ); 00130 addFrame( dBounds, dstBitDepth, dstComponents, t, i++ ); 00131 } 00132 } 00133 00134 template<class View> 00135 void NLMDenoiserProcess<View>::preProcess() 00136 { 00137 // Initialize progress bar 00138 if( _paramOptimized->getValue() ) 00139 { 00140 const int min_xpi = std::min( (int) _paramRegionRadius->getValue() * 2, (int) _srcViews[0].width() ), 00141 min_ypi = std::min( (int) _paramRegionRadius->getValue() * 2, (int) _srcViews[0].height() ); 00142 std::stringstream msg; 00143 msg << "NL-Means algorithm in progress (automatic bandwidth = " << computeBandwidth() << ")."; 00144 this->progressBegin( _paramDepth->getValue() * min_xpi * min_ypi, msg.str() ); 00145 } 00146 else 00147 { 00148 this->progressBegin( (int) ( ( _srcViews[0].width() * this->_renderArgs.renderScale.x ) * 00149 ( _srcViews[0].height() * this->_renderArgs.renderScale.y ) / 00150 _plugin._clipSrc->getPixelAspectRatio() ), "NL-Means algorithm in progress" ); 00151 } 00152 } 00153 00154 /** 00155 * @brief Function called by rendering thread each time a process must be done. 00156 * @param[in] procWindowRoW Processing window in RoW 00157 */ 00158 template<class View> 00159 void NLMDenoiserProcess<View>::multiThreadProcessImages( const OfxRectI& procWindowRoW ) 00160 { 00161 NlmParams params; 00162 // Change reference point 00163 params.mix[0] = (float) _paramRedStrength->getValue(); 00164 params.mix[1] = (float) _paramGreenStrength->getValue(); 00165 params.mix[2] = (float) _paramBlueStrength->getValue(); 00166 params.mix[2] = 1.0; 00167 00168 params.bws[0] = (float) _paramRedGrainSize->getValue(); 00169 params.bws[1] = (float) _paramGreenGrainSize->getValue(); 00170 params.bws[2] = (float) _paramBlueGrainSize->getValue(); 00171 params.bws[3] = 1.0; 00172 00173 params.patchRadius = _paramPatchRadius->getValue(); 00174 params.regionRadius = _paramRegionRadius->getValue(); 00175 params.preBlurring = (float) _paramPreBlurring->getValue(); 00176 00177 // Destination subview cropped by the procwindow 00178 View subDst = bgil::subimage_view( this->_dstView, 00179 procWindowRoW.x1 - this->_renderArgs.renderWindow.x1, 00180 procWindowRoW.y1 - this->_renderArgs.renderWindow.y1, 00181 procWindowRoW.x2 - procWindowRoW.x1, 00182 procWindowRoW.y2 - procWindowRoW.y1 ); 00183 nlMeans( subDst, procWindowRoW, params ); 00184 } 00185 00186 template<class View> 00187 double NLMDenoiserProcess<View>::computeBandwidth() 00188 { 00189 double bandwidth = 0.0; 00190 // Assume that we work in RGB 00191 const int nc = 3; 00192 double np = ( 2 * _paramPatchRadius->getValue() ) * ( 2 * _paramPatchRadius->getValue() ) * nc; 00193 if( np < 100 ) 00194 bandwidth = ( ( ( ( ( ( 1.1785e-12 * np - 5.1827e-10 ) * np + 9.5946e-08 ) * np - 9.7798e-06 ) * np + 6.0756e-04 ) * np - 0.0248 ) * np + 1.9203 ) * np + 7.9599; 00195 else 00196 bandwidth = ( -7.2611e-04 * np + 1.3213 ) * np + 15.2726; 00197 00198 return bandwidth / _paramDepth->getValue(); 00199 } 00200 00201 /** 00202 * @brief Function called to apply nl-means denoising 00203 * 00204 * @param[out] dst Destination image view 00205 * @param[in] procWindow 00206 * @param[in] params 00207 * 00208 */ 00209 template<class View> 00210 void NLMDenoiserProcess<View>::nlMeans( View &dst, const OfxRectI & procWindow, const NlmParams & params ) 00211 { 00212 using namespace boost::gil; 00213 using namespace terry; 00214 00215 typedef typename rgba32f_view_t::x_iterator WeightIt; 00216 typedef typename View::x_iterator x_iterator; 00217 typedef typename View::locator Locator; 00218 00219 const int w = dst.width(); 00220 const int h = dst.height(); 00221 00222 static const int nc = num_channels<Pixel>::value; 00223 boost::array<float, nc> mix; 00224 00225 for( std::size_t i = 0; i < mix.size(); ++i ) 00226 { 00227 mix[i] = params.mix[i] * channel_traits< Channel >::max_value(); 00228 } 00229 00230 std::vector<View> subSrcViews; 00231 typename std::vector<View>::iterator it; 00232 const int margin = params.regionRadius + params.patchRadius; 00233 00234 // Upscale process window 00235 OfxRectI upscaledProcWindow, tUpscaledProcWindow; 00236 upscaledProcWindow.x1 = procWindow.x1 - margin - 1; 00237 upscaledProcWindow.y1 = procWindow.y1 - margin - 1; 00238 upscaledProcWindow.x2 = procWindow.x2 + margin + 1; 00239 upscaledProcWindow.y2 = procWindow.y2 + margin + 1; 00240 upscaledProcWindow = rectanglesIntersection( upscaledProcWindow, _upScaledBounds ); 00241 00242 // Translate 00243 tUpscaledProcWindow.x1 = upscaledProcWindow.x1 - _upScaledBounds.x1; 00244 tUpscaledProcWindow.y1 = upscaledProcWindow.y1 - _upScaledBounds.y1; 00245 tUpscaledProcWindow.x2 = upscaledProcWindow.x2 - _upScaledBounds.x1; 00246 tUpscaledProcWindow.y2 = upscaledProcWindow.y2 - _upScaledBounds.y1; 00247 00248 // Create up-scaled-proc-windowed subviews sequence 00249 for( it = _srcViews.begin(); it != _srcViews.end(); ++it ) 00250 { 00251 subSrcViews.push_back( subimage_view( *it, tUpscaledProcWindow.x1, tUpscaledProcWindow.y1, 00252 tUpscaledProcWindow.x2 - tUpscaledProcWindow.x1, 00253 tUpscaledProcWindow.y2 - tUpscaledProcWindow.y1 ) ); 00254 } 00255 00256 // Allocate average buffers (assimilate this two buffers as 2D float buffers, not images !) 00257 rgba32f_image_t weight_cumul( w, h ); /// @todo: use memory allocated by host using memorySuite 00258 rgba32f_image_t weight_norm( w, h ); 00259 rgba32f_view_t view_wc( view( weight_cumul ) ); 00260 rgba32f_view_t view_norm( view( weight_norm ) ); 00261 00262 // Initialize averaging buffer to 0 00263 fill_black( view_wc ); 00264 fill_black( view_norm ); 00265 00266 // Bugs bunny is here 00267 OfxRectI nProcWindow; 00268 nProcWindow.x1 = ( procWindow.x1 - upscaledProcWindow.x1 ); 00269 nProcWindow.y1 = ( procWindow.y1 - upscaledProcWindow.y1 ); 00270 nProcWindow.x2 = nProcWindow.x1 + w; 00271 nProcWindow.y2 = nProcWindow.y1 + h; 00272 00273 computeWeights( subSrcViews, nProcWindow, view_wc, view_norm, params ); 00274 00275 if( !_plugin.abort() ) 00276 { 00277 View procView = subimage_view( subSrcViews[0], nProcWindow.x1, nProcWindow.y1, w, h ); 00278 for( int yj = 0; yj < h; ++yj ) 00279 { 00280 WeightIt wcIter = view_wc.row_begin( yj ); 00281 WeightIt wnIter = view_norm.row_begin( yj ); 00282 x_iterator src_it = procView.row_begin( yj ); 00283 x_iterator dst_it = dst.row_begin( yj ); 00284 for( int xj = 0; xj < w; ++xj ) 00285 { 00286 // Final estimate 00287 for( int v = 0; v < nc; ++v ) 00288 { 00289 ( *dst_it )[v] = ( ( ( *wcIter )[v] * mix[v] + 1.0f * ( *src_it )[v] ) / ( ( *wnIter )[v] * mix[v] + 1.0f ) ); 00290 } 00291 00292 // Fill alpha 00293 assign_channel_if_exists_t<Pixel, alpha_t>()( *src_it, *dst_it ); 00294 00295 ++src_it; 00296 ++dst_it; 00297 ++wcIter; 00298 ++wnIter; 00299 } 00300 this->progressForward( w ); 00301 } 00302 } 00303 } 00304 00305 template<class View> 00306 void NLMDenoiserProcess<View>::computeWeights( const std::vector<View> & srcViews, 00307 const OfxRectI & procWindow, 00308 bgil::rgba32f_view_t & view_wc, 00309 bgil::rgba32f_view_t & view_norm, 00310 const NlmParams & params ) 00311 { 00312 typedef typename View::x_iterator sIterator; 00313 typedef typename bgil::rgba32f_view_t::x_iterator wIterator; 00314 typedef typename bgil::channel_type<View>::type dpix_t; 00315 typedef typename View::locator Loc; 00316 typedef typename bgil::rgba32f_view_t::locator WLoc; 00317 00318 const int patchRadius = params.patchRadius; 00319 const int depth = srcViews.size(); 00320 00321 const int wi = srcViews[0].width(); 00322 const int hi = srcViews[0].height(); 00323 00324 // Noise variance estimation 00325 const double nv = imageUtils::noise_variance( srcViews[0] ); 00326 const double sigma = std::sqrt( nv < 0 ? 0 : nv ); 00327 Loc loc1, loc2; 00328 WLoc wcLoc, wnLoc; 00329 00330 // Optimisation based on: AN IMPROVED NON-LOCAL DENOISING ALGORITHM, LNLA 2008 00331 // Define the size of the neighborhood 00332 const int min_xpi = std::min( params.regionRadius, wi / 2 ); 00333 const int min_ypi = std::min( params.regionRadius, hi / 2 ); 00334 00335 static const int nc = boost::mpl::min< boost::mpl::int_<3>, typename bgil::num_channels<Pixel>::type >::type::value; 00336 boost::array<float,nc> bws; 00337 for( std::size_t i = 0; i < bws.size(); ++i ) 00338 { 00339 bws[i] = params.bws[i]; 00340 } 00341 int lbound, hbound; 00342 // [Kervrann] notations 00343 std::vector<double> h1( nc ); 00344 std::vector<double> h2( nc ); 00345 00346 for( int i = 0; i < nc; ++i ) 00347 { 00348 if( bws[i] < 0 ) 00349 bws[i] = (float) computeBandwidth(); 00350 h1[i] = bws[i] * sigma; 00351 h2[i] = 1.0 / ( h1[i] * h1[i] ); 00352 } 00353 00354 double abs_e, eucl_dist, weigth, e; 00355 00356 // For zi (displacment) 00357 for( int zi = 0; zi < depth; ++zi ) 00358 { 00359 // For yi (displacment) 00360 for( int yi = -min_ypi; yi <= min_ypi; ++yi ) 00361 { 00362 // For xi (displacment) 00363 for( int xi = -min_xpi; xi <= min_xpi; ++xi ) 00364 { 00365 // If not 0 displacment 00366 if( xi != 0 || yi != 0 ) 00367 { 00368 const int xl = xi < 0 ? std::abs( xi ) : 0; 00369 const int xh = wi + xi > wi ? wi - xi : wi; 00370 const int yl = yi < 0 ? std::abs( yi ) : 0; 00371 const int yh = hi + yi > hi ? hi - yi : hi; 00372 00373 // For yj 00374 for( int yj = yl; yj < yh; ++yj ) 00375 { 00376 // Initialize patch euclidian distance 00377 eucl_dist = 0.0f; 00378 int j = yj + yi; 00379 // Horizontal & vertical averaging bounds 00380 if( yi >= 0 ) 00381 { 00382 lbound = -patchRadius + yj < 0 ? std::max( -yj, -patchRadius ) : -patchRadius; 00383 hbound = lbound + ( patchRadius * 2 + 1 ); 00384 if( hbound + j > hi ) 00385 hbound = std::min( hi - j, patchRadius ); 00386 } 00387 else 00388 { 00389 hbound = patchRadius + yj > hi ? std::min( patchRadius, ( hi - yj ) ) : patchRadius; 00390 lbound = hbound - ( patchRadius * 2 + 1 ); 00391 if( lbound + j < 0 ) 00392 lbound = 0; 00393 } 00394 00395 // Warmup: initial average accumulation 00396 int xl_bound = std::max( xl - patchRadius, 0 ); 00397 int xr_bound = std::min( wi, xl_bound + patchRadius * 2 ); 00398 loc1 = srcViews[zi].xy_at( xi, j ); 00399 loc2 = srcViews[ 0].xy_at( 0, yj ); 00400 for( int xj = xl_bound; xj < xr_bound; ++xj ) 00401 { 00402 // following "if" is bad but simplify the code 00403 if( ( xj + xi ) >= 0 ) 00404 { 00405 for( int k = lbound; k < hbound; ++k ) 00406 { 00407 for( int v = 0; v < nc; ++v ) 00408 { 00409 e = loc1( xj, k )[v] - loc2( xj, k )[v]; 00410 eucl_dist += e * e; 00411 } 00412 } 00413 } 00414 } 00415 00416 // For xj 00417 for( int xj = xl; xj < xh; ++xj ) 00418 { 00419 int i = xj + xi; 00420 00421 loc1 = srcViews[zi].xy_at( i, j ); 00422 loc2 = srcViews[0].xy_at( xj, yj ); 00423 00424 // 2D centered patch sliding average 00425 if( patchRadius + i < wi && patchRadius + xj < wi ) 00426 { 00427 for( int k = lbound; k < hbound; ++k ) 00428 { 00429 for( int v = 0; v < nc; ++v ) 00430 { 00431 e = loc1( patchRadius, k )[v] - loc2( patchRadius, k )[v]; 00432 eucl_dist += e * e; 00433 } 00434 } 00435 } 00436 00437 if( xj - patchRadius - 1 >= 0 && i - patchRadius - 1 >= 0 ) 00438 { 00439 for( int k = lbound; k < hbound; ++k ) 00440 { 00441 for( int v = 0; v < nc; ++v ) 00442 { 00443 e = loc1( -patchRadius - 1, k )[v] - loc2( -patchRadius - 1, k )[v]; 00444 eucl_dist -= e * e; 00445 } 00446 } 00447 } 00448 00449 // Symetric weigthening will be computed 00450 bool w1Pass = ( zi == 0 && i >= procWindow.x1 && i < procWindow.x2 && 00451 j >= procWindow.y1 && j < procWindow.y2 ); 00452 00453 // Weigthening will be computed 00454 bool w2Pass = ( xj >= procWindow.x1 && xj < procWindow.x2 && 00455 yj >= procWindow.y1 && yj < procWindow.y2 ); 00456 00457 // Weight computation (Modified Bisquare weightening function) 00458 if( w1Pass || w2Pass ) 00459 { 00460 wcLoc = view_wc.xy_at( xj - procWindow.x1, yj - procWindow.y1 ); 00461 wnLoc = view_norm.xy_at( xj - procWindow.x1, yj - procWindow.y1 ); 00462 00463 abs_e = std::abs( eucl_dist ); 00464 for( int v = 0; v < nc; ++v ) 00465 { 00466 if( abs_e <= h1[v] ) 00467 { 00468 weigth = 1.0 - ( abs_e * abs_e * h2[v] ); 00469 // Powerize to 8 00470 weigth *= weigth; 00471 weigth *= weigth; 00472 weigth *= weigth; 00473 00474 // Weight accumulation 00475 if( w1Pass ) 00476 { 00477 wcLoc( xi, yi )[v] += weigth * loc2( 0, 0 )[v]; 00478 wnLoc( xi, yi )[v] += weigth; 00479 } 00480 if( w2Pass ) 00481 { 00482 // Symmetry 00483 wcLoc( 0, 0 )[v] += weigth * loc1( 0, 0 )[v]; 00484 wnLoc( 0, 0 )[v] += weigth; 00485 } 00486 } 00487 } 00488 } 00489 } // End for xj 00490 } // End for yj 00491 } // End if not 0 displacment 00492 if( this->progressForward( 1 ) ) /// @todo what is the step here ?? 00493 return; 00494 } // End for xi (displacment) 00495 } // End for yi (displacment) 00496 } // End for zi (displacment) 00497 } 00498 00499 } 00500 } 00501 }