2
  1. I got an image like this where I'D like to apply some special type of filter on to:

img0

  1. I got a RGB color (49, 76, 14) for the dark green area (the biggest one).

So I'd like to get some kind of threshold property for my filtering purposes: I'd like to filter each of the image pixels to check if it's color is near to the given RGB value (49, 76, 14). The threshold symbolizes the offset-rate.

Threshold small :
             --> `(49, 76, 14) close to (48, 75, 13)`
             --> `(49, 76, 14) "not" close to (50, 75, 13)`

Threshold big :
             --> `(49, 76, 14) close to (48, 75, 13)`
             --> `(49, 76, 14) "also" close to (50, 75, 13)`

Filtering the image with the smallest threshold just the given rgb color will get filtered:

img1

A bigger threshold will also filter colors near the given rgb color:

img2

How can I achieve coding a function that is able to check if one color is close to another color (+threshold property) (+using rgb values)?

Note:

This was the code I was thinking above first but testing it - it doesn't really lead to success (by filtering the given value the small left-top area should get filtered at first because it's color does nearly look like the given one. But this code is first matching the gray one that is obviously not matching to the given color!)

 var offset = (threshold/100.0) * 255.0
 var color = [r,g,b]
 var r = redvalue, g = greenvalue, b = bluevalue
 if !(
      (r >= color[0]-offset) &&
      (r <= color[0]+offset) &&
      (g >= color[1]-offset) &&
      (g <= color[1]+offset) &&
      (b >= color[2]-offset) &&
      (b <= color[2]+offset)
     ) { // apply filter }

Any help would be very appreciated. Some source code written in Swift or JavaScript or even some PseudoCode would be also fine.

Edit: One spontaneous idea was to convert the rgb-colors int lab-colors to afterward compute the euclidean distance from the two colors. But I'm not sure if this solution will work and also not if it is the most efficient one :)

  • There are some interesting ideas [here](https://stackoverflow.com/q/5392061/1630618) – vacawama Mar 12 '18 at 17:05
  • I wrote an app that names colours in an image by comparing the "distance" of the colour to the canonical RGB hexes of named colours (ISTR I used the X11 list) - I ended up having to do it in HSL (Hue Saturation Lightness) rather than RGB, with a weight of 10 on the hue - arbitrary, but that seemed to work pretty well. – Grimxn Mar 12 '18 at 18:19
  • Would you be so kind to share some code snippets? `:)` @Grimxn –  Mar 12 '18 at 18:31
  • I think you are mixing float values with integers which causes your rounding error as both colors you show in examples has the same distance to the green color you compare with .... `48` and `50` are the same distance to `49` ... try to use `offset = round((threshold/100.0) * 255.0);` or `floor` or `ceil` ....I usually use distance. you can handle colors as 3D vectors so subtract them and compute the distance... no need for sqrt you can compare distance^2 with threshold^2 ... see [Detectings small circles on game minimap](https://stackoverflow.com/a/49232820/2521214) where I do exactly that. – Spektre Mar 13 '18 at 07:44
  • if you need better color detection you can use HSV which handle colors more like human perception does. – Spektre Mar 13 '18 at 07:44

2 Answers2

1

This isn't a full answer, but an elaboration of a suggestion I made in a comment that the OP asked I provide some code for.

I have an app that names colours by calculating the Euclidean distance to the canonical named colours in the X11 list of triples (similar to what the OP is trying to do but for a different purpose). I found, as he did, that using the RGB space does not give intuitive answers, so I transformed each colour into the Hue Saturation Lightness (HSL) dimensions, and then gave a higher weight to the Hue dimension than the other two. Here is the actual "distance" code, and the supporting functions for transforming the dimensions:

private let oneThird = 1.0 / 3.0
private let twoThirds = 2.0 / 3.0

func hsl(red: Double, green: Double, blue: Double) -> (hue: Double, saturation: Double, lightness: Double) {
    let minv = min(red, green, blue)
    let maxv = max(red, green, blue)
    let diff = maxv - minv
    let sum = maxv + minv

    let l = sum * 0.5
    var s = 0.0
    var h = 0.0

    if (diff > 0.0) {
        if (l < 0.5) {
            s = diff / sum
        } else {
            s = diff / (2.0 - sum)
        }

        let dr = (((maxv - red) / 6.0) + (diff / 2.0)) / diff
        let dg = (((maxv - green) / 6.0) + (diff / 2.0)) / diff
        let db = (((maxv - blue) / 6.0) + (diff / 2.0)) / diff

        if red == maxv {
            h = db - dg
        } else if green == maxv {
            h = oneThird + dr - db
        } else if blue == maxv {
            h = twoThirds + dg - dr
        }
    }
    return (modp(h, denominator: 1.0), s, l)
}

func modp<T: BinaryFloatingPoint>(_ numerator : T, denominator : T) -> T {
    // acts as fmod, but assures the result is positive between 0 and fabs(denominator)
    let myDenominator = abs(denominator)
    var myNumerator = numerator.remainder(dividingBy: myDenominator)
    //var myNumerator = fmod(numerator, myDenominator)
    while (myNumerator < 0) { myNumerator += myDenominator }
    return myNumerator
}

let (h1, s1, l1) = hsl(red: r1, green: g1, blue: b1) // The reference colour
let (h2, s2, l2) = hsl(red: r2, green: g2, blue: b2) // The colour to be tested
let (dh, ds, dl) = (h1 - h2, s1 - s2, l1 - l2)
let hueWeight = 10 // Arbitrary, but works.
let distance = sqrt(hueWeight * dh * dh + ds * ds + dl * dl)

You can then compare your distance value against your thresholds...

The original source of the HSL formulae was, I think, here...

p.s. I've used Double for these values - that's because I was using my own colour type - if you're implementing it with UIColor, you'd be better using CGFloats...

Grimxn
  • 22,115
  • 10
  • 72
  • 85
0

Your concept of "close by some threshold" is related to color model of a color space.

A color model has three or four dimensions. RGB operates in 3 dimensions (or components).

The idea of having an sphere of radius "threshold" centered on the target-color is not bad. Just be aware of negative coordinates if you set the boundary of that sphere.

If RGBcolor model does not give you enough "precision" then you can change to CIELAB or HSL models; But then you need to play with several thresholds at once, due to the different meaning of each "dimension" in those color spaces.

Ripi2
  • 7,031
  • 1
  • 17
  • 33
  • It's more a case of *weights* for the dimensions if you use HSL rather than three different thresholds - and the weights can be fixed - see my comment to the OP. – Grimxn Mar 12 '18 at 18:25