## 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 |