Algorithm for 'pixelated circle' image recognition

Viewed 346

1st image 2nd image 3rd image

Here are three sample images. In these images I want to find:

  1. Coordinates of the those small pixelated partial circles.
  2. Rotation of these circles. These circle have a 'pointy' side. I want to find its direction.

For example, coordinates and the angle with positive x axis of that small partial circle in the

  1. first image is (51 px, 63 px), 240 degrees, respectively.
  2. second image is (50 px, 52 px), 300 degrees, respectively.
  3. third image is (80 px, 29 px), 225 degrees, respectively.

I don't care about scale invariance.

Methods I have tried:

  1. ORB feature detection
  2. SIFT feature detection

Feature detection don't seem to work here.

enter image description here

Above is the example of ORB feature detector finding similar features in 1st and 2nd image. It is finding one correct match, rest are wrong.

Probably because these images are too low resolution to find any meaningful corners or blobs. The corners and blob it does find are not much different form other pixelated object present.

I have seen people use erosion and dilution to remove noise, but my objects are too small for that to work.

Perhaps some other feature detector can help?

I am also thinking about Generalized Hough transform, however I cant find a complete tutorial to implement it with OpenCV (c++). Also I want something that is fast. Hopefully in real time.

Any help is appreciated.

1 Answers

If the small circles have constant size, then you might try a convolution.

This is a quick and dirty test I ran with ImageMagick for speed, and coefficients basically pulled out of thin air:

convert test1.png  -define convolve:scale='!' -morphology Convolve \
"12x12: \
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 \
-9,-7,-2,-1,0,0,0,0,-1,-2,-7,-9 \
-9,-2,-1,0,9,9,9,9,0,-1,-2,-9 \
-9,-1,0,9,7,7,7,7,9,0,-1,-9 \
-9,0,9,7,-9,-9,-9,-9,7,9,0,-9 \
-9,0,9,7,-9,-9,-9,-9,7,9,0,-9 \
-9,0,9,7,-9,-9,-9,-9,7,9,0,-9 \
-9,0,9,7,-9,-9,-9,-9,7,9,0,-9 \
-9,-1,0,9,7,7,7,7,9,0,-1,-9 \
-9,-2,0,0,9,9,9,9,0,0,-2,-9 \
-9,-7,-2,-1,0,0,0,0,-1,-2,-7,-9 \
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9" \
test2.png

I then ran a simple level stretch plus contrast to bring out what already were visibly more luminous pixels, and a sharpen/reduction to shrink pixel groups to their barycenters (these last operations could be done by multiplying the matrix by the proper kernel), and got this.

The source image on the left is converted to the output on the right, the pixels above a certain threshold mean "circle detected".

Once this is done, I imagine the "pointy" end can be refined with a modified quicunx - use a 3x3 square grid centered on the center pixel, count the total luminosity in each of the eight peripheral squares, and that ought to give you a good idea of where the "point" is. You might want to apply thresholding to offset a possible blurring of the border (the centermost circle in the example below, the one inside the large circle, could give you a false reading).

For example, if we know the coordinates of the center in the grayscale matrix M, and we imagine the circle having diameter of 7 pixels (this is more or less what the convolution above says), we would do

uint quic[3][3] = { { 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } };

for (y = -3; y <= 3; y++) {    
    for (x = -3; x <= 3; x++) {
        if (matrix[cy+y][cx+x] > threshold) {
            quic[(y+3)/2-1][(x+3)/2-1] += matrix[cy+y][cx+x];
        }
    }
}
// Now, we find which quadrant in quic holds the maximum:
// if it is, say, quic[2][0], the point is southeast.
//   0  1  2  x
// 0 NE N NW
// 1 E  X  W
// 2 SE S SW
// y
// Value X (1,1) is totally unlikely - the convolution would
// not have found the circle in the first place if it was so

For an accurate result you would have to use "sub-pixel" addressing, which is slightly more complicated. With the method above, one of the circles results in these quicunx values, that give a point to the southeast:

enter image description here

Needless to say, with this kind of resolution the use of a finer grid is pointless, you'd get an error of the same order of magnitude.

I've tried with some random doodles and the convolution matrix has a good rejection of non-signal shapes, but of course this is due to information about the target's size and shape - if that assumption fails, this approach will be a dead end.

It would help to know the image source: there're several tricks used in astronomy and medicine to detect specific shapes or features.

enter image description here

Python opencv2

The above can be implemented with Python:

#!/usr/bin/python3
import cv2
import numpy as np

# Scaling factor
d = 240

kernel1 = np.array([
[ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 ],
[ -9,-7,-2,-1,0,0,0,0,-1,-2,-7,-9 ],
[ -9,-2,-1,0,9,9,9,9,0,-1,-2,-9 ],
[ -9,-1,0,9,7,7,7,7,9,0,-1,-9 ],
[ -9,0,9,7,-9,-9,-9,-9,7,9,0,-9 ],
[ -9,0,9,7,-9,-9,-9,-9,7,9,0,-9 ],
[ -9,0,9,7,-9,-9,-9,-9,7,9,0,-9 ],
[ -9,0,9,7,-9,-9,-9,-9,7,9,0,-9 ],
[ -9,-1,0,9,7,7,7,7,9,0,-1,-9 ],
[ -9,-2,0,0,9,9,9,9,0,0,-2,-9 ],
[ -9,-7,-2,-1,0,0,0,0,-1,-2,-7,-9 ],
[ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 ]
], dtype = np.single)

sharpen = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]);


image = cv2.imread('EuDpD.png')

# Scale kernel
for i in range(0, 12):
    for j in range(0, 12):
        kernel1[i][j] = kernel1[i][j]/d

identify = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

# Sharpen image
identify = cv2.filter2D(src=identify, ddepth=-1, kernel=sharpen)

# Cut at ~90% of maximum

ret,thresh = cv2.threshold(identify, 220, 255, cv2.THRESH_BINARY)

cv2.imwrite('identify.png', thresh)

The above, ran on the grayscaled image (left), gives the following result (right). A better sharpening or adaptive thresholding could come up with a single pixel.

enter image description here

Related