How to get coordinates of white dots on black background

4.8k views Asked by At

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?

3

There are 3 answers

3
Mark Setchell On BEST ANSWER

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.

0
Adi Shavit On

Many (most?) image processing libraries (e.g. OpenCV) have a connected components extraction module. Given a binary image like in your example, you will get a list of all connected image regions. You can calculate the centroid of these to get the center of your dots.

0
Peanut On

One way would be to use a "Hough Transform". The hough transform basically transforms an image to another coordinate system where you can "easily" detect round shapes.

I found a nodejs-example in another question. I'm not sure though whether it worked successful for them: hough transform - javascript - node.js

You could also loop through each pixel of your image and check whether it is white or not, and if it's white, find all it's white neighbors and remove them. Calulate their average x-y-coordinates and voila, you've got the coordinates of your dot. This probably works well if you don't have too much noise.