skip to Main Content

What I am trying to achieve is similar to photoshop/gimp’s eyedropper tool: take a round sample of a given area in an image and return the average colour of that circular sample.

The simplest method I have found is to take a ‘regular’ square sample, mask it as a circle, then reduce it to 1 pixel, but this is very CPU-demanding (especially when repeated millions of times).

A more mathematically complex method is to take a square area and average only the pixels that fall within a circular area within that sample, but determining what pixel is or isn’t within that circle, repeated, is CPU-demanding as well.

Is there a more succinct, less-CPU-demanding means to achieve this?

2

Answers


  1. Chosen as BEST ANSWER

    I'm sure that there's a more succinct way to go about it, but:

    import math
    import numpy as np
    import imageio as ioimg # as scipy's i/o function is now depreciated
    from skimage.draw import circle
    import matplotlib.pyplot as plt
    
    
    # base sample dimensions (rest below calculated on this). 
    # Must be an odd number.
    wh = 49
    # tmp - this placement will be programmed later
    dp = 500
    
    #load work image (from same work directory)
    img = ioimg.imread('830.jpg')
    # convert to numpy array (droppying the alpha while we're at it)
    np_img = np.array(img)[:,:,:3]
    # take sample of resulting array
    sample = np_img[dp:wh+dp, dp:wh+dp]
    
    #==============
    # set up numpy circle mask
    ## this mask will be multiplied against each RGB layer in extracted sample area
    
    # set up basic square array
    sample_mask = np.zeros((wh, wh), dtype=np.uint8)
    # set up circle centre coords and radius values
    xy, r = math.floor(wh/2), math.ceil(wh/2)
    # use these values to populate circle area with ones
    rr, cc = circle(xy, xy, r)
    sample_mask[rr, cc] = 1
    # add axis to make array multiplication possible (do I have to do this)
    sample_mask = sample_mask[:, :, np.newaxis]
    
    result = sample * sample_mask
    # count number of nonzero values (this will be our median divisor)
    nz = np.count_nonzero(sample_mask)
    
    sample_color = []
    for c in range(result.shape[2]):
        sample_color.append(int(round(np.sum(result[:,:,c])/nz)))
    
    
    print(sample_color) # will return array like [225, 205, 170]
    plt.imshow(result, interpolation='nearest')
    plt.show()
    

    resulting circle-sample Perhaps asking this question here wasn't necessary (it has been a while since I've python-ed, and was hoping that some new library had been developed for this since), but I hope this can be a reference for others who have the same goal.

    This operation will be performed for every pixel in the image (sometimes millions of times) for thousands of images (scanned pages), so therein are my performance issue worries, but thanks to numpy, this code is pretty quick.


  2. Here’s a little example of skimage.draw.circle() which doesn’t actually draw a circle but gives you the coordinates of points within a circle which you can use to index Numpy arrays with.

    #!/usr/bin/env python3
    
    import numpy as np
    from skimage.io import imsave
    from skimage.draw import circle
    
    # Make rectangular canvas of mid-grey
    w, h = 200, 100
    img = np.full((h, w), 128, dtype=np.uint8)
    
    # Get coordinates of points within a central circle
    Ycoords, Xcoords = circle(h//2, w//2, 45)
    
    # Make all points in circle=200, i.e. fill circle with 200
    img[Ycoords, Xcoords] = 200
    
    # Get mean of points in circle
    print(img[Ycoords, Xcoords].mean())      # prints 200.0
    
    # DEBUG: Save image for checking
    imsave('result.png',img)
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search