Search code examples
node.jsimage-processingwebcam-capture

How to get coordinates of white dots on black background


Some background:

I'm building an AR art installation, and need to track a person as they move through a room.

To do this I've built a head piece that has several infrared lights (with diffusers) and have a camera (a USB webcam) that has an optical filter to remove most/all visible light from the image, as well as a few tweaks to the image that basically leave me with white dots on a black background.

Getting the webcam set up in such a way as to capture the boundaries of the room was pretty easy, but I'm unsure as to how to go about then processing the black and white image to get the x,y coordinates of each dot.

Example image output: (This is a mock-up as I don't have one on me this second, and also keep in mind that the data will come from what is effectively a video) enter image description here

Tools I'm using

  • NodeJS for processing
  • Logitech Webcam for image capture
  • Google Cardboard for visuals
  • Infrared leds in styrofoam balls for nice diffuse light points

Any ideas?


Solution

  • I can think of three ways to do this with ImageMagick, which has node bindings, and is installed on most Linux distros and is available for OSX and Windows.

    First, at the command line, quite simply type this:

    identify -precision 5 -define identify:locate=maximum -define identify:limit=3 image.png
    Channel maximum locations:
    Gray: 65535 (1) 146,164 147,164 148,164
    

    and that shows you the three brightest pixels are 146 pixels across from the top left corner and 164 pixels down from the top left corner, and the two beside it to its right.

    Alternatively, if you are interested in the area and/or centroid of the dot, you can do a Connected Components Analysis with ImageMagick, which goes like this:

    convert image.png                               \
        -colorspace gray -threshold 10%             \
        -define connected-components:verbose=true   \
        -connected-components 8 output.png
    
    Objects (id: bounding-box centroid area mean-color):
    0: 818x502+0+0 408.6,250.5 410539 srgb(0,0,0)
    1: 11x11+143+164 148.0,169.0 97 srgb(255,255,255)
    

    This shows you (in the last line of output) that the white blob is 11 pixels x 11 pixels and is located 143 pixels across the image from the left edge and 164 pixels down from the top. Its centroid is at 148,169 and its area is 97 pixels and its colour is white.

    The first object found (in the second to last line of output) is the entire image and you can discount that as its colour is black, i.e. rgb(0,0,0).

    I can explain the parameters a little too... I convert to grayscale because Coonected Component Analysis traditionally looks for white objects on a black background in a b&w image. I then threshold to get pure white and pure black - you may need a median filter here on your real system to get rid of noise -median 3, for example. The verbose=true means that the command should print a list of all the blobs it finds, and the 8 means to consider pixels that are 8-connected to be parts of the same blob, i.e. a pixel touching another one at its NE, SE, SW, or NW corner is considered part of the same blob - if you set that to 4, pixels have to be directly beside or above/below one another to be considered neighbours.

    If you want to "box in" the area that it has found, you can do that like this:

    convert image.png -stroke red -fill none -strokewidth 2 -draw "rectangle 143,164 154,175" output.png
    

    enter image description here

    The third method is slower, and it involves converting the image to text and then searching for the word "white". So. let's start simple, and just convert the image to text like this:

    convert image.png -threshold 50% txt:
    # ImageMagick pixel enumeration: 818,502,255,srgb
    0,0: (0,0,0)  #000000  black
    1,0: (0,0,0)  #000000  black
    2,0: (0,0,0)  #000000  black
    3,0: (0,0,0)  #000000  black
    ...
    ... 410,000 lines later
    ...
    813,501: (0,0,0)  #000000  black
    814,501: (0,0,0)  #000000  black
    815,501: (0,0,0)  #000000  black
    816,501: (0,0,0)  #000000  black
    817,501: (0,0,0)  #000000  black
    

    Now, let's refine that, and look for white pixels only (on Windows you would use FINDSTR rather than grep):

    convert image.png -threshold 50% txt: | grep white
    146,164: (255,255,255)  #FFFFFF  white
    147,164: (255,255,255)  #FFFFFF  white
    148,164: (255,255,255)  #FFFFFF  white
    149,164: (255,255,255)  #FFFFFF  white
    150,164: (255,255,255)  #FFFFFF  white
    145,165: (255,255,255)  #FFFFFF  white
    146,165: (255,255,255)  #FFFFFF  white
    147,165: (255,255,255)  #FFFFFF  white
    148,165: (255,255,255)  #FFFFFF  white
    149,165: (255,255,255)  #FFFFFF  white
    150,165: (255,255,255)  #FFFFFF  white
    151,165: (255,255,255)  #FFFFFF  white
    144,166: (255,255,255)  #FFFFFF  white
    145,166: (255,255,255)  #FFFFFF  white
    146,166: (255,255,255)  #FFFFFF  white
    147,166: (255,255,255)  #FFFFFF  white
    148,166: (255,255,255)  #FFFFFF  white
    149,166: (255,255,255)  #FFFFFF  white
    150,166: (255,255,255)  #FFFFFF  white
    151,166: (255,255,255)  #FFFFFF  white
    152,166: (255,255,255)  #FFFFFF  white
    143,167: (255,255,255)  #FFFFFF  white
    144,167: (255,255,255)  #FFFFFF  white
    145,167: (255,255,255)  #FFFFFF  white
    146,167: (255,255,255)  #FFFFFF  white
    147,167: (255,255,255)  #FFFFFF  white
    148,167: (255,255,255)  #FFFFFF  white
    149,167: (255,255,255)  #FFFFFF  white
    150,167: (255,255,255)  #FFFFFF  white
    151,167: (255,255,255)  #FFFFFF  white
    152,167: (255,255,255)  #FFFFFF  white
    153,167: (255,255,255)  #FFFFFF  white
    143,168: (255,255,255)  #FFFFFF  white
    144,168: (255,255,255)  #FFFFFF  white
    145,168: (255,255,255)  #FFFFFF  white
    146,168: (255,255,255)  #FFFFFF  white
    147,168: (255,255,255)  #FFFFFF  white
    148,168: (255,255,255)  #FFFFFF  white
    149,168: (255,255,255)  #FFFFFF  white
    150,168: (255,255,255)  #FFFFFF  white
    151,168: (255,255,255)  #FFFFFF  white
    152,168: (255,255,255)  #FFFFFF  white
    153,168: (255,255,255)  #FFFFFF  white
    143,169: (255,255,255)  #FFFFFF  white
    144,169: (255,255,255)  #FFFFFF  white
    145,169: (255,255,255)  #FFFFFF  white
    146,169: (255,255,255)  #FFFFFF  white
    147,169: (255,255,255)  #FFFFFF  white
    148,169: (255,255,255)  #FFFFFF  white
    149,169: (255,255,255)  #FFFFFF  white
    150,169: (255,255,255)  #FFFFFF  white
    151,169: (255,255,255)  #FFFFFF  white
    152,169: (255,255,255)  #FFFFFF  white
    153,169: (255,255,255)  #FFFFFF  white
    143,170: (255,255,255)  #FFFFFF  white
    144,170: (255,255,255)  #FFFFFF  white
    145,170: (255,255,255)  #FFFFFF  white
    146,170: (255,255,255)  #FFFFFF  white
    147,170: (255,255,255)  #FFFFFF  white
    148,170: (255,255,255)  #FFFFFF  white
    149,170: (255,255,255)  #FFFFFF  white
    150,170: (255,255,255)  #FFFFFF  white
    151,170: (255,255,255)  #FFFFFF  white
    152,170: (255,255,255)  #FFFFFF  white
    153,170: (255,255,255)  #FFFFFF  white
    143,171: (255,255,255)  #FFFFFF  white
    144,171: (255,255,255)  #FFFFFF  white
    145,171: (255,255,255)  #FFFFFF  white
    146,171: (255,255,255)  #FFFFFF  white
    147,171: (255,255,255)  #FFFFFF  white
    148,171: (255,255,255)  #FFFFFF  white
    149,171: (255,255,255)  #FFFFFF  white
    150,171: (255,255,255)  #FFFFFF  white
    151,171: (255,255,255)  #FFFFFF  white
    152,171: (255,255,255)  #FFFFFF  white
    153,171: (255,255,255)  #FFFFFF  white
    144,172: (255,255,255)  #FFFFFF  white
    145,172: (255,255,255)  #FFFFFF  white
    146,172: (255,255,255)  #FFFFFF  white
    147,172: (255,255,255)  #FFFFFF  white
    148,172: (255,255,255)  #FFFFFF  white
    149,172: (255,255,255)  #FFFFFF  white
    150,172: (255,255,255)  #FFFFFF  white
    151,172: (255,255,255)  #FFFFFF  white
    152,172: (255,255,255)  #FFFFFF  white
    145,173: (255,255,255)  #FFFFFF  white
    146,173: (255,255,255)  #FFFFFF  white
    147,173: (255,255,255)  #FFFFFF  white
    148,173: (255,255,255)  #FFFFFF  white
    149,173: (255,255,255)  #FFFFFF  white
    150,173: (255,255,255)  #FFFFFF  white
    151,173: (255,255,255)  #FFFFFF  white
    146,174: (255,255,255)  #FFFFFF  white
    147,174: (255,255,255)  #FFFFFF  white
    148,174: (255,255,255)  #FFFFFF  white
    149,174: (255,255,255)  #FFFFFF  white
    150,174: (255,255,255)  #FFFFFF  white
    

    As regards a node version, I am really not very good at node but I can point you to my answer here which does another ImageMagick process through node and hope you can adapt that if you try the above at the command line and find it works for you well enough that you want to use ImageMagick.