cyberalaska / include / rasterCV / bullseye.cpp @ master
History  View  Annotate  Download (4.59 KB)
1 
/**


2 
OpenCV bullseye detector library: uses a gradientvoting scheme.

3 

4 
Dr. Orion Sky Lawlor, lawlor@alaska.edu, 20131106 (Public Domain)

5 
*/

6 
#include "bullseye.h" 
7 
#include <algorithm> /* for std::sort */ 
8  
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.cols4,accum.rows4); 
21 
if (!cv::clipLine(r,S,E)) return; 
22 

23 
float rounding=0.49999; // compensates for rounding down 
24 

25 
int dx=E.xS.x;

26 
int dy=E.yS.y;

27 
if (abs(dx)>abs(dy))

28 
{ /* Xmajor line */

29 
if (E.x<S.x) std::swap(S,E);

30 
float m=(E.yS.y)/float(E.xS.x); 
31 
float b=S.ym*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 
{ /* Ymajor 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.xS.x)/float(E.yS.y); 
45 
float b=S.xm*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(xdx*s,ydy*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(xdx*s,ydy*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.rowsde;y++) 
118 
for (int x=de;x<accum.colsde;x++) 
119 
{ 
120 
int cur=fetchAccum(accum,x,y);

121 
if (cur>=minimumVotesPerEye)

122 
{ /* it's bigbut 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; // subpixel center 
140  
141 
float C=fetchAccum(accum,x,y); // 5 point stencil here 
142 
float L=fetchAccum(accum,x1,y), R=fetchAccum(accum,x+1,y); 
143 
float B=fetchAccum(accum,x,y1), T=fetchAccum(accum,x,y+1); 
144  
145 
cx+=(RL)/(2.0*(R+L2.0*C)); // parabolic peakpolishing, b/2a 
146 
cy+=(TB)/(2.0*(T+B2.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  
161 