improc

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

作成した関数

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 )
}

clamp = function( array, min = 0, max = 1 ){
  array[ array < min ] = min
  array[ array > max ] = max
  return( array )
}

luminance = function( cimg, weight = "Lab" ){
  if( spectrum( cimg ) < 3 ){
    return( imsub( cimg, cc == 1 ) )
  } else if( spectrum( cimg ) > 3 ){
    cimg = imsub( cimg, cc < 4 )
  }
  
  # For definitions of lightness formulas, see https://en.wikipedia.org/wiki/HSL_and_HSV#Lightness
  if( is.character( weight ) ){
    if( weight == "Lab" ){
      return( as.array( imsub( sRGBtoLab( cimg ), cc == 1 ) * 0.01 ) )
    } else if( weight == "HSI" ){
      return( as.array( imsub( RGBtoHSI( sRGBtoRGB( cimg ) ), cc == 3 ) ) )
    } else if( weight == "HSV" ){
      return( as.array( imsub( RGBtoHSV( sRGBtoRGB( cimg ) ), cc == 3 ) ) )
    } else if( weight == "HSL" ){
      return( as.array( imsub( RGBtoHSL( sRGBtoRGB( cimg ) ), cc == 3 ) ) )
    } else if( weight == "Luma" ){
      return( 0.3 * as.array( R( cimg ) ) + 0.59 * as.array( G( cimg ) ) + 0.11 * as.array( B( cimg ) ) )
    } else {
      print( "Caution: set a proper value for weight." )
      return( as.array( cimg ) )
    }
  } else if( is.numeric( weight ) & length( weight ) == 3 ){
    weight = weight / sum( weight )
    return( weight[1] * as.array( R(cimg) ) +weight[2] * as.array( G(cimg) ) + weight[3] * as.array( B(cimg) ) )
  } else {
    print( "Caution: set a proper value for weight." )
    return( as.array( cimg ) )
  }
}

lum.mean = function( cimg, weight = "Lab" ){
  return( mean( luminance( cimg, weight = weight ) ) )
}

lum.sd = function( cimg, weight = "Lab" ){
  return( sd( luminance( cimg, weight = weight ) ) )
}

load.image.dir = function( dir, as.grayscale = F ){
  names = list.files( dir, pattern = "\\.(jpg|jpeg|png|bmp|JPG|JPEG|PNG|BMP)$" )
  l = vector( "list", length( names ) )
  for( i in 1:length( names ) ){
    l[[ i ]] = imager::load.image( paste0( dir, "/", names[ i ] ) )
    if( as.grayscale ){
      l[[ i ]] = as.grayscale( l[[ i ]] )
    }
  }
  names( l ) = names
  return( l )
}

使用例。

# 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.image.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 ) )

コメント

Copied title and URL