ヒストグラムマッチングによる画像の平均輝度とコントラストの調整

作成した関数

hist.match = function( cimg, target.hist = NULL, return.image = T, save.dir = "NULL" ){
  if( !is.null( save.dir ) ){
    dir.create( file.path( save.dir ), showWarnings = F )
  }
  if( class( cimg )[ 1 ] != "list" ){
    cimg = list( image.png = cimg )
  }
  if( is.null( target.hist ) ){
    h = lapply( cimg, hist.create )
    target.hist = hist.average( h )
  }
  
  progress = 1
  staging = unique( floor( seq( from = 1, to = length( cimg ), length.out = 5 ) ) )
  cat( "[hist.match] Progress: " )
  for( i in 1:length( cimg ) ){
    # Display progress
    if( i == staging[ progress ] ){
      cat( paste0( sprintf( "%1.0f", 100 * ( i - 1 ) / length( cimg ) ), "% ... " ) )
      progress = progress + 1
    }
    # Matching routine
    target = hist.scale( target.hist, sum( h[[ i ]] ) )
    if( spectrum( cimg[[ i ]] ) < 3 ){
      L = as.data.frame( cimg[[ i ]] )
      L = L[ order( L$value), ]
      L$value = hist.serialize( target ) / 255
      im = as.cimg( L, dim = dim( cimg[[ i ]] ) )
    } else if( spectrum( cimg[[ i ]] ) >= 3 ){
      cimg[[ i ]] = imsub( cimg[[ i ]], cc < 4 )
      # saparate L and ab
      imdf = as.data.frame( sRGBtoLab( cimg[[ i ]] ) )
      imdf.L = subset( imdf, cc == 1 )
      imdf.L = imdf.L[ order( imdf.L$value), ]
      # match luminance
      imdf.L$value = hist.serialize( target ) * 100 / 255
      # reconstruct image
      im = rbind( imdf.L, subset( imdf, cc > 1 ) )
      im = LabtosRGB( as.cimg( im, dim = dim( cimg[[ i ]] ) ) )
    }
    
    if( return.image ){
      cimg[[ i ]] = im
    }
    if( ! is.null( save.dir ) ){
      imager::save.image( im, paste0( save.dir, "/", names( cimg )[ i ] ) )
    }
  }
  cat( "done!\n" )
  
  if( return.image ){
    if( length( cimg ) == 1 ){
      return( cimg[[ 1 ]] )
    } else {
      return( cimg )
    }
  }
}

hist.create = function( cimg, weight = "Lab" ){
  L = clamp( round( luminance( cimg, weight = weight ) * 255 ), 0, 255 )
  L = table( L )
  h = vector( "integer", 256 )
  h[ as.integer( names( L ) ) + 1 ] = L
  return( h )
}

hist.scale = function( hist, target.n ){
  h = round( hist * target.n / sum( hist ) )
  diff = sum( h ) - target.n
  if( diff != 0 ){
    s = sort( h, decreasing = T, index.return = T )
    h[ s$ix[ 1:abs( diff ) ] ] = h[ s$ix[ 1:abs( diff ) ] ] - sign( diff )
  }
  return( h )
}

hist.normalize = function( hist ){
  return( hist * 1 / sum( hist ) )
}

hist.serialize = function( hist ){
  lum.values = vector( "numeric", sum( hist ) )
  index = 1
  for( l in 1:256 ){
    if( hist[ l ] == 0 ){
      next
    }
    for( p in 1:hist[ l ] ){
      lum.values[ index ] = l - 1
      index = index + 1
    }
  }
  return( lum.values )
}

hist.average = function( list.hist ){
  h = lapply( list.hist, hist.normalize )
  average.hist = vector( "numeric", 256 )
  for( i in 1:length( h ) ){
    average.hist = average.hist + h[[ i ]]
  }
  return( average.hist )
}

使用例。

# display luminance histgram
h = hist.create( boats )
plot( h, type = "h", xlab = "Luminance", ylab = "Number of pixels" )

# averaging histgrams
im = list( imager::load.image( "../img/face.jpg" ), imager::load.image( "../img/forest.jpg" ) )
h = lapply( im, hist.create )
h2 = hist.average( h )
layout( t( 1:3 ) )
plot( h[[ 1 ]], type = "h", main = "face", xlab = "Luminance", ylab = "Number of pixels" )
plot( h[[ 2 ]], type = "h", main = "forest", xlab = "Luminance", ylab = "Number of pixels" )
plot( h2, type = "h", main = "averaged", xlab = "Luminance", ylab = "Number of pixels" )
layout( 1 )

# images and histgrams before the match
im = load.img.dir( "../img" )
im = lapply( im, imresize, scale = .5 )
# im = lapply( im, as.grayscale )
h = lapply( im, hist.create )
layout( matrix( 1:(length( im ) * 2), 2, length( im ), byrow = F ) )
for( i in 1:length( im ) ){
  plot( im[[ i ]] )
  plot( h[[ i ]], type = "h", xlab = "Luminance", ylab = "Number of pixels" )
}
layout( 1 )

# images and histgrams after the match
im = load.img.dir( "../img" )
im = lapply( im, imresize, scale = .5 )
im = hist.match( im )
h = lapply( im, hist.create )
layout( matrix( 1:(length( im ) * 2), 2, length( im ), byrow = F ) )
for( i in 1:length( im ) ){
  plot( im[[ i ]] )
  plot( h[[ i ]], type = "h", xlab = "Luminance", ylab = "Number of pixels" )
}
layout( 1 )

# mean and sd of luminance are almost identical to each other after the match
unlist( lapply( im, lum.mean ) )
unlist( lapply( im, lum.sd ) )

コメント