Statistics
| Branch: | Revision:

cyberalaska / include / rasterCV / bullseye.cpp @ master

History | View | Annotate | Download (4.59 KB)

1 4f01d71b Dr. Orion Lawlor
/**
2
OpenCV bulls-eye detector library: uses a gradient-voting scheme.
3

4
Dr. Orion Sky Lawlor, lawlor@alaska.edu, 2013-11-06 (Public Domain)
5
*/
6
#include "bullseye.h"
7 baed3a36 Dr. Orion Lawlor
#include <algorithm> /* for std::sort */
8 4f01d71b Dr. Orion Lawlor
9
10
typedef unsigned short accum_t;
11
inline accum_t fetchAccum(const cv::Mat &accum,int x,int y) {
12
        return ((const accum_t *)accum.data)[y*accum.cols+x];
13
}
14
15
/* Increment pixels along this line. */
16
static void accumulateLine(cv::Mat &accum,
17
        cv::Point S,cv::Point E)
18
{
19
        accum_t *accumDat=(accum_t *)accum.data;
20
        cv::Rect r(2,2,accum.cols-4,accum.rows-4);
21
        if (!cv::clipLine(r,S,E)) return;
22
        
23
        float rounding=0.49999; // compensates for rounding down
24
        
25
        int dx=E.x-S.x;
26
        int dy=E.y-S.y;
27
        if (abs(dx)>abs(dy)) 
28
        { /* X-major line */
29
                if (E.x<S.x) std::swap(S,E);
30
                float m=(E.y-S.y)/float(E.x-S.x);
31
                float b=S.y-m*S.x+rounding;
32
                for (int x=S.x;x<=E.x;x++)
33
                {
34
                        float y=m*x+b;
35
                        //if (y<0 || y>=accum.rows) abort();
36
                        accumDat[((int)y)*accum.cols+x]+=1.0;
37
                }
38
        }
39
        else  /* dx<=dy */
40
        { /* Y-major line */
41
                if (E.y==S.y) return; // start and end are equal
42
                
43
                if (E.y<S.y) std::swap(S,E);
44
                float m=(E.x-S.x)/float(E.y-S.y);
45
                float b=S.x-m*S.y+rounding;
46
                for (int y=S.y;y<=E.y;y++)
47
                {
48
                        float x=m*y+b;
49
                        //if (x<0 || x>=accum.cols) abort();
50
                        accumDat[y*accum.cols+(int)x]+=1.0;
51
                }
52
        }
53
}
54
55
56
/**
57
  Find a list of bullseyes in this grayscale (single channel) source image.
58
*/
59
bullseyeList findBullseyes(const cv::Mat &grayImage, // source grayscale image, use cv::cvtColor(colorImg,grayImg,CV_BGR2GRAY);
60
        double minimumGradientMagnitude, // gradient steepness required to draw line (low values slower but can detect weaker)
61
        double minimumVotesPerEye, // minimum vote count to be an eye (low values more sensitive but false positives)
62
        double gradientVotePixels, // number of pixels to extend gradient (low values faster but only support small eyes)
63
        int minimumEyeDistance // minimum distance between distinct bullseyes
64
        )
65
{
66
        bullseyeList bulls;
67
        
68
        // Accumulator for gradient power.  
69
        //  CV_8U doesn't have enough bits for typical vote counts.
70
        cv::Mat accum=cv::Mat::zeros(grayImage.rows,grayImage.cols,CV_16U);
71
        
72
/* Convert steep gradients to lines */
73
        // Gradient estimate (with filtering)
74
        int ksize=3; // pixel count for gradient filter+blur
75
        // 22fps, 98% of CPU with float gradients:
76
        const int grad_typecode=CV_32F;
77
        typedef float grad_t;
78
        
79
        /* 
80
        // 23fps, 98% of CPU with unsigned short gradients: 
81
        const int grad_typecode=CV_16S;
82
        typedef signed short grad_t;
83
        */
84
        
85
        cv::Mat gradX,gradY;
86
        cv::Sobel(grayImage,gradX,grad_typecode, 1,0, ksize);
87
        cv::Sobel(grayImage,gradY,grad_typecode, 0,1, ksize);
88
        
89
        grad_t *gradXF=(grad_t *)gradX.data;
90
        grad_t *gradYF=(grad_t *)gradY.data;
91
        float minDiffSq=minimumGradientMagnitude*minimumGradientMagnitude;
92
        for (int y=0;y<grayImage.rows;y++)
93
        for (int x=0;x<grayImage.cols;x++)
94
        {
95
                int i=y*grayImage.cols+x;
96
                float dx=gradXF[i], dy=gradYF[i];
97
                float magSq=dx*dx+dy*dy; // squared magnitude of gradient vector
98
                if (magSq>minDiffSq)  
99
                {
100
                        float mag=sqrt(magSq); // now a length
101
                        float s=gradientVotePixels/mag; // scale factor from gradient to line length
102
                        accumulateLine(accum,
103
                                cv::Point(x+dx*s,y+dy*s),
104
                                cv::Point(x-dx*s,y-dy*s));
105
                        
106
                        /* // cv::line doesn't support alpha blending (WHY NOT?!)
107
                        cv::line(annot,
108
                                cv::Point(x+dx*s,y+dy*s),
109
                                cv::Point(x-dx*s,y-dy*s),
110
                                cv::Scalar(255,0,0,10),0.1,CV_AA);
111
                        */
112
                }
113
        }
114
        
115
// Circle areas where there's a high gradient *and* a local maximum.
116
        int de=minimumEyeDistance; // must be maximum among neighborhood of this many pixels (==min distance between eyes)
117
        for (int y=de;y<accum.rows-de;y++)
118
        for (int x=de;x<accum.cols-de;x++)
119
        {
120
                int cur=fetchAccum(accum,x,y);
121
                if (cur>=minimumVotesPerEye) 
122
                { /* it's big--but is there a bigger one nearby? */
123
                        bool biggest=true;
124
                        for (int dy=-de;dy<de && biggest;dy++)
125
                        for (int dx=-de;dx<de;dx++)
126
                        {
127
                                float her=fetchAccum(accum,x+dx,y+dy);
128
                                /* To break ties, I'm putting a slight tilt along both axes. */
129
                                her+=dx*(1.0/1057)+dy*(1.0/8197);
130
                                
131
                                if (cur<her) {
132
                                        biggest=false;
133
                                        break;
134
                                }
135
                        }
136
                        
137
                        if (biggest) 
138
                        { /* This is a bullseye! */
139
                                double cx=x, cy=y; // sub-pixel center
140
141
                                float C=fetchAccum(accum,x,y); // 5 point stencil here
142
                                float L=fetchAccum(accum,x-1,y), R=fetchAccum(accum,x+1,y);
143
                                float B=fetchAccum(accum,x,y-1), T=fetchAccum(accum,x,y+1);
144
145
                                cx+=-(R-L)/(2.0*(R+L-2.0*C)); // parabolic peak-polishing, -b/2a
146
                                cy+=-(T-B)/(2.0*(T+B-2.0*C)); 
147
                                
148
                                bullseyeInfo eye;
149
                                eye.x=cx; eye.y=cy;
150
                                eye.votes=cur;
151
                                bulls.eyes.push_back(eye);
152
                        }
153
                }
154
        }
155
        
156
        // Sort by ascending size
157
        std::sort(bulls.eyes.begin(),bulls.eyes.end());
158
        return bulls;
159
}
160