Attachment @ Test image for color, grayscale, or single hue gradient file_download
2021-06-21
«img-test.rb»
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | #!/usr/bin/env ruby # How to check image color or back and white -- http://www.imagemagick.org/discourse-server/viewtopic.php?t=19580 # # convert file.jpg -colorspace HSL -verbose info:|grep -A 6 Green # values: absolute (percent) # Green: # min: 0 (0) # max: 255 (1) # mean: 70.2043 (0.275311) # standard deviation: 99.2055 (0.389041) # kurtosis: -0.981812 # skewness: 0.897517 # # You convert the image to HSL and then output verbose information ("identify" # statistics). # In HSL space Saturation define how colorful pixels are, and this is returned # in the 'Green' channel statistics. # # If max is 0 so no color was present at all: it is a perfect grayscale or black/white image. # If max is 1 so at least one pure color pixel exists: it is not a pure grayscale image. # # The mean indicates how much colorful in general the image is. # A mean of 40% means the image is not even mostly grayscale (white background). # # Other statistics show how the color ratios was distributed about that mean. # # NOTE: # Due to HSL being a double hexcone model, this colorspace does not work properly # for measuring saturation for L>50% as it approaches the top peak. There white # is going to be indetermined saturation and turns out is interpreted as high # saturation. In its place, use HCL, HCLp, HSI or HSB above. These are all single # hexcone type models and should work fine in the above equation. # # convert file.jpg -colorspace HSI -channel g +channel -format "%[fx:mean.g]" info: # # ------------------------------------------------------------------------------ # # How to retrive the single values from the statistics: # Retrieving individual Image Channel Statistics -- http://www.imagemagick.org/discourse-server/viewtopic.php?t=21008 # FX Expressions as Format and Annotate Escapes -- http://www.imagemagick.org/Usage/transform/#fx_escapes # Format and Print Image Properties -- http://www.imagemagick.org/script/escape.php # The Fx Special Effects Image Operator -- http://www.imagemagick.org/script/fx.php # # ------------------------------------------------------------------------------ # # Robust command to get H/S/I statistics in the R/G/B sections: # convert file.jpg -colorspace HSB -verbose info: | grep -A 20 Red # # Get the single value of a statistic (saturation mean): # convert file.jpg -colorspace HSB -channel g +channel -format "%[fx:mean.g]" info: # # Get image colors histogram for a restricted number of colors (64): # convert file.jpg -dither FloydSteinberg -colors 64 -colorspace HSB -format %c histogram:info:- $VERBOSE = nil require 'shellwords' EXIT_CODES = { colored: 0, colored_mid_sat: 1, grayscale: 2, monochrome: 3, monochrome_mid_sat: 4, error: 100, } if ARGV.size == 0 puts "USAGE: #{File.basename __FILE__} img.ext [-h] [-s] [-q]" puts " FLAGS:" puts " -h show histograms" puts " -s show statistics" puts " -q quiet mode" puts " EXIT CODES:" EXIT_CODES.each{|k, n| puts " #{n.to_s.ljust 3} #{k}"} exit EXIT_CODES[:error] end def test_image(fname, opts = {}) opts = { hist: false, stats: false, quiet: true }.merge opts unless File.exists?(fname) STDERR.puts "ERROR: file not found" unless opts[:quiet] return :error end # computing histogram histlines = `convert #{fname.shellescape} -dither FloydSteinberg -colors 64 -colorspace HSB -format %c histogram:info:- 2> /dev/null`.split("\n") puts "----- full histogram -----\n#{histlines.join "\n"}" if opts[:hist] # extract values from histogram (num.pixels, hue, saturation, brightness) hist = histlines.grep(/hs[ib]/i).map{|line| # http://www.imagemagick.org/Usage/files/#histogram # pixel count: R G B #HEX hsb(hue, saturation, brightness) # 9274: ( 1, 89,135) #015987 hsb(0.582895%,35.0103%,53.0617%) next unless m = line.match(Regexp.new ' +([0-9]+):.+#([0-9A-F]+) hs[ib]a*\(([0-9\.]+)(%*),([0-9\.]+)%*,([0-9\.]+)%') hue = m.captures[2].to_f # percentage values: 0-100 (old imagemagick version) hue = hue/360*100 if m.captures[3].to_s.size == 0 # degrees 0-360 => convert to 0-100 hue = hue.round(4) { npix: m.captures[0].to_i, hex: m.captures[1], hue: hue, hue2: (hue > 50 ? (hue-50) : (hue+50)).round(4), # 180 degrees shifted HUE sat: m.captures[4].to_f, int: m.captures[5].to_f, } }.compact if hist.size < 2 STDERR.puts "ERROR: histogram size (#{hist.size}) < 2" unless opts[:quiet] return :error end # compute saturation mean and unique hue std.deviation removed_rows = [] if opts[:hist] puts "----- new histogram -----" puts hist[0].keys.map{|i| "#{i} ".upcase.rjust(10)}.join(" | ") end stats = hist.each_with_index.inject({ tot_pixels: 0, sat_sum: 0, hue_num: 0, hue_mean: 0, hue_m2: 0, hue2_num: 0, hue2_mean: 0, hue2_m2: 0, hue_outlayered_pixels: 0, }){|stats, pair| row, i = pair stats[:tot_pixels] += row[:npix] stats[:sat_sum] += row[:npix] * row[:sat] if %w{ 000000 FFFFFF }.include?(row[:hex]) || row[:sat] < 10 # remove not only the pure black and white colors, but any color that has a # very, very low saturation (greys). When the saturation is near zero, the # hue has very little meaning, and could point in any direction. stats[:hue_outlayered_pixels] += row[:npix] removed_rows << row else # incremental/online formula for std.deviation # https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm # compute hue std.dev delta = row[:hue] - stats[:hue_mean] stats[:hue_num ] += 1 stats[:hue_mean] += delta / stats[:hue_num] stats[:hue_m2 ] += delta * (row[:hue] - stats[:hue_mean]) # FIX the red hue that wraps around the 360 dregree: # Compute again the std.dev with the hues rotated 180 degress. # You will either get roughly the same standard deviation, or wildly different. # In the second case, one of the std.dev would probably be very small, indicating # an image with a red midtone coloring. delta = row[:hue2] - stats[:hue2_mean] stats[:hue2_num ] += 1 stats[:hue2_mean] += delta / stats[:hue2_num] stats[:hue2_m2 ] += delta * (row[:hue2] - stats[:hue2_mean]) puts row.values.map{|i| i.to_s.rjust(10)}.join(" | ") if opts[:hist] end stats }#hist.each_with_index.inject if opts[:hist] puts ' --- deleted ---' puts hist[0].keys.map{|i| "#{i} ".upcase.rjust(10)}.join(" | ") puts removed_rows.map{|l| l.values.map{|i| i.to_s.rjust(10)}.join(" | ")} end hist_size = hist.size - removed_rows.size sat_mean = stats[:sat_sum] / stats[:tot_pixels] hue_sd = (stats[:hue_m2 ] / (hist_size - 1)) ** 0.5 if hist_size > 3 hue2_sd = (stats[:hue2_m2] / (hist_size - 1)) ** 0.5 if hist_size > 3 min_hue = [hue_sd, hue2_sd].min if hist_size > 3 hue_outl = stats[:hue_outlayered_pixels].to_f / stats[:tot_pixels] * 100 img_type = \ if hist_size == 0 ; :grayscale elsif hist_size <= 3 ; :monochrome elsif sat_mean < 10 ; :grayscale # low saturation # middle saturation: hue must be big for a colored image elsif sat_mean < 30 ; min_hue > 25 ? :colored_mid_sat : :monochrome_mid_sat else ; :colored # high saturation end puts([ fname.ljust(30), 'S:%5.2f' % sat_mean , 'H:%5.2f' % hue_sd .to_f, 'H2:%5.2f' % hue2_sd.to_f, 'N:%2d' % hist.size, 'R:%2d' % removed_rows.size, 'O:%3d |