1
votes

I recently had to merge images like these:

a.pngb.png

These images represents different sorts of events happening at different locations. The idea is to merge these images in a way to keep the "hot" zones of each images (red-yellow-green) to get a global picture of what happens globally.

In my current approach, I take take the second image and extracts the red/green channel, in order to form a mask of the relevant parts, like this:

b-mask.png

Then I merge it with the first image by using this mask so only the relevant parts gets copied over.

Here is the script used for this:

#!/bin/bash

# Extract RGB
convert b.png -colorspace RGB -separate b-sep-%d.png

# Keep red & green only
convert b-sep-2.png b-sep-0.png -compose minus -composite b-tmp-br.png
convert b-sep-2.png b-sep-1.png -compose minus -composite b-tmp-bg.png
convert b-tmp-br.png b-tmp-bg.png -compose plus -composite -level 10%,100% b-mask.png

# Composite!
composite b.png a.png b-mask.png final.png

Here is my current result so far:

enter image description here

As you can see, it works well for the red-yellow-green part, but the blue part is missing. The problem is that if I enlarge the mask to include the blue part, then it'll overwrite red-yellow-green parts from the first image with blue parts from the second one! This is already visible in the final result, the top left first image red part is overwritten by the green part of the second image.

Getting the blue part correctly is trickier, but I think the following algorithm should work (pseudo code):

function merge_pixel(pixel a, pixel b)
{
  points = { :red => 4, :yellow => 3, :green => 2, :blue => 1, :default => 0 }
  a_points = points[a.color()]
  b_points = points[b.color()]
  return a_points > b_points ? a : b
}

That is, when merging images, copy the pixel from image a or b depending on which color is the most important for the final image. Maybe this algorithm isn't sound (e.g how to handle the gradient part, maybe with a threshold), feel free to debunk it.

REAL QUESTION:

using imagemagick, how to:

  1. get the desired result using any technique/whatever?
  2. implement the algorithm from above?

You don't need to answer both questions, just finding an imagemagick way of getting the desired result is fine.

[EDIT]

Hint: I just had an idea, I think you can generate the masks (including blue parts) for both images and do some "set intersection/union/difference/whatever" of the masks to generate the appropriate "final" mask so only the real relevant parts of image b is copied over.

1

1 Answers

0
votes

Ok, I did the "merge_pixel" strategy and it worked!

require 'RMagick'
include Magick

def pixel_score(p)
  r, g, b = [p.red, p.green, p.blue].map{ |i| i / 256 }

  is_flat   = (r-g).abs < 20 && (r-b).abs < 20 && (g-b).abs < 20

  is_grey   = is_flat && r < 200

  is_red    = r >= 240 && g <= 100 # && b < 10
  is_yellow = r >= 150 && g >= 100 && b <= 10
  is_green  = r <= 200 && g >= 200 && b <= 100
  is_cyan   = r <= 10  && g >= 100 && b >= 30
  is_blue   = r <= 10  && g <= 100 && b >= 200

  if is_red
    (999**8) + (r - g)
  elsif is_yellow
    (999**7) + (r + g)
  elsif is_green
    (999**6) + (g - b)
  elsif is_cyan
    (999**5) + (g + b)
  elsif is_blue
    (999**4) + (b - g)
  else
    (999**1) + r ** 3 + g ** 2 + b
  end
end

def rmagick_merge(file_a, file_b, file_merged)
  img_a = ImageList.new(file_a)
  img_b = ImageList.new(file_b)
  result = Image.new(img_a.columns, img_a.rows)
  img_a.columns.times do |col|
    img_a.rows.times do |row|
      pixel_a = img_a.pixel_color(col, row)
      pixel_b = img_b.pixel_color(col, row)

      pixel = [pixel_a, pixel_b].sort_by{ |p| pixel_score(p) }.last
      #pixel = [pixel_a, pixel_b].sort_by{ |p| [p.red - p.green, p.green, p.blue] }.first
      #pixel = [pixel_a, pixel_b].sort_by{ |p| [p.red - p.green - p.blue * 100, p.green, p.blue] }.last

      result.pixel_color(col, row, pixel)
    end
  end
  result.format = "PNG"
  result.write(file_merged)
end

if __FILE__ == $0
  if ARGV.size < 3
    puts "usage #{__FILE__} a.png b.png merged.png"
    exit 1
  end
  rmagick_merge(ARGV[0], ARGV[1], ARGV[3])
end

Here is the result (not perfect but fine tuned for my needs on the real pictures):

ok.png