1 #include <cmath>
2 #include <sstream>
3 #include <iostream>
4 #include <algorithm>
5 #include "mfpf_hog_box_finder_builder.h"
6 //:
7 // \file
8 // \brief Builder for mfpf_hog_box_finder objects.
9 // \author Tim Cootes
10
11 #include <mfpf/mfpf_hog_box_finder.h>
12 #include "vsl/vsl_binary_loader.h"
13 #include "vul/vul_string.h"
14 #include <cassert>
15
16 #include <mbl/mbl_parse_block.h>
17 #include <mbl/mbl_read_props.h>
18 #if 0
19 #include <mbl/mbl_exception.h>
20 #endif
21
22 #include "vil/vil_resample_bilin.h"
23 #include "vil/vil_image_view.h"
24 #include "vsl/vsl_vector_io.h"
25 #include "vsl/vsl_indent.h"
26 #ifdef _MSC_VER
27 # include "vcl_msvc_warnings.h"
28 #endif
29
30 #include <mipa/mipa_orientation_histogram.h>
31 #include <mipa/mipa_sample_histo_boxes.h>
32 #include <mipa/mipa_identity_normaliser.h>
33 #include <mipa/mipa_block_normaliser.h>
34 #include <mipa/mipa_ms_block_normaliser.h>
35
36 //: Divide elements of v by sum of last nA elements
37 // For histogram vectors these are the total sums
mfpf_norm_histo_vec(vnl_vector<double> & v,unsigned nA)38 inline void mfpf_norm_histo_vec(vnl_vector<double>& v, unsigned nA)
39 {
40 unsigned n=v.size();
41 double sum = 0.0;
42 for (unsigned i=1;i<=nA;++i) sum+=v[n-i];
43 v/=sum;
44 }
45
46 //=======================================================================
47 // Dflt ctor
48 //=======================================================================
49
mfpf_hog_box_finder_builder()50 mfpf_hog_box_finder_builder::mfpf_hog_box_finder_builder():normaliser_(mipa_identity_normaliser())
51 {
52 set_defaults();
53 }
54
55 //: Define default values
set_defaults()56 void mfpf_hog_box_finder_builder::set_defaults()
57 {
58 step_size_=1.0;
59 search_ni_=5;
60 search_nj_=5;
61 nA_=0;
62 dA_=0.0;
63
64 nA_bins_=8;
65 full360_=true;
66 nc_=2;
67 ni_=0;
68 nj_=0;
69 ref_x_=0;
70 ref_y_=0;
71 //norm_method_=0;
72 overlap_f_=1.0;
73 }
74
75 //=======================================================================
76 // Destructor
77 //=======================================================================
78
79 mfpf_hog_box_finder_builder::~mfpf_hog_box_finder_builder() = default;
80
81 //: Create new mfpf_hog_box_finder on heap
new_finder() const82 mfpf_point_finder* mfpf_hog_box_finder_builder::new_finder() const
83 {
84 return new mfpf_hog_box_finder();
85 }
86
set_angle_bins(unsigned nA_bins,bool full360,unsigned cell_size)87 void mfpf_hog_box_finder_builder::set_angle_bins(unsigned nA_bins,
88 bool full360, unsigned cell_size)
89 {
90 nA_bins_ = nA_bins;
91 full360_ = full360;
92 nc_ = cell_size;
93 reconfigure_normaliser();
94 }
95
96 //: Define region size in world co-ordinates
97 // Sets up ROI to cover given box (with samples at step_size()),
98 // with ref point at centre.
set_region_size(double wi,double wj)99 void mfpf_hog_box_finder_builder::set_region_size(double wi, double wj)
100 {
101 wi/=(2*nc_*step_size());
102 wj/=(2*nc_*step_size());
103 int ni = std::max(2,int(0.99+wi));
104 int nj = std::max(2,int(0.99+wj));
105 set_as_box(unsigned(ni),unsigned(nj),0.5*(ni-1),0.5*(nj-1));
106 }
107
108
109 //: Define model region as an ni x nj box
set_as_box(unsigned ni,unsigned nj,double ref_x,double ref_y,const mfpf_vec_cost_builder & builder)110 void mfpf_hog_box_finder_builder::set_as_box(unsigned ni, unsigned nj,
111 double ref_x, double ref_y,
112 const mfpf_vec_cost_builder& builder)
113 {
114 set_as_box(ni,nj,ref_x,ref_y);
115 cost_builder_ = builder.clone();
116 reconfigure_normaliser();
117 }
118
119 //: Define model region as an ni x nj box
set_as_box(unsigned ni,unsigned nj,double ref_x,double ref_y)120 void mfpf_hog_box_finder_builder::set_as_box(unsigned ni, unsigned nj,
121 double ref_x, double ref_y)
122 {
123 ni_=ni; nj_=nj;
124
125 ref_x_=ref_x;
126 ref_y_=ref_y;
127 reconfigure_normaliser();
128 }
129
130
131 //: Define model region as an ni x nj box
set_as_box(unsigned ni,unsigned nj,const mfpf_vec_cost_builder & builder)132 void mfpf_hog_box_finder_builder::set_as_box(unsigned ni, unsigned nj,
133 const mfpf_vec_cost_builder& builder)
134 {
135 set_as_box(ni,nj, 0.5*(ni-1),0.5*(nj-1), builder);
136 reconfigure_normaliser();
137 }
138
139
140 //: Initialise building
141 // Must be called before any calls to add_example(...)
clear(unsigned n_egs)142 void mfpf_hog_box_finder_builder::clear(unsigned n_egs)
143 {
144 unsigned n_per_eg = (1+2*nA_);
145 cost_builder().clear(n_egs*n_per_eg);
146 }
147
148 //: Add one example to the model
add_one_example(const vimt_image_2d_of<float> & image,const vgl_point_2d<double> & p,const vgl_vector_2d<double> & u)149 void mfpf_hog_box_finder_builder::add_one_example(
150 const vimt_image_2d_of<float>& image,
151 const vgl_point_2d<double>& p,
152 const vgl_vector_2d<double>& u)
153 {
154 vgl_vector_2d<double> u1=step_size_*u;
155 vgl_vector_2d<double> v1(-u1.y(),u1.x());
156
157 assert(image.image().nplanes()==1);
158
159 // Set up sample area with 1 unit border
160 unsigned sni = 2+2*nc_*ni_;
161 unsigned snj = 2+2*nc_*nj_;
162 vil_image_view<float> sample(sni,snj);
163
164 const vgl_point_2d<double> p0 = p-(1+nc_*ref_x_)*u1-(1+nc_*ref_y_)*v1;
165
166 const vimt_transform_2d& s_w2i = image.world2im();
167 vgl_point_2d<double> im_p0 = s_w2i(p0);
168 vgl_vector_2d<double> im_u = s_w2i.delta(p0, u1);
169 vgl_vector_2d<double> im_v = s_w2i.delta(p0, v1);
170
171 vil_resample_bilin(image.image(),sample,
172 im_p0.x(),im_p0.y(), im_u.x(),im_u.y(),
173 im_v.x(),im_v.y(),
174 sni,snj);
175
176 vil_image_view<float> histo_im;
177 mipa_orientation_histogram(sample,histo_im,nA_bins_,nc_,full360_);
178
179 vnl_vector<double> v;
180 mipa_sample_histo_boxes_3L(histo_im,0,0,v,ni_,nj_);
181
182 normaliser_->normalise(v);
183 //if (norm_method_==1) mfpf_norm_histo_vec(v,nA_bins_);
184
185 cost_builder().add_example(v);
186 }
187
188 //: Add one example to the model
add_example(const vimt_image_2d_of<float> & image,const vgl_point_2d<double> & p,const vgl_vector_2d<double> & u)189 void mfpf_hog_box_finder_builder::add_example(const vimt_image_2d_of<float>& image,
190 const vgl_point_2d<double>& p,
191 const vgl_vector_2d<double>& u)
192 {
193 if (nA_==0)
194 {
195 add_one_example(image,p,u);
196 return;
197 }
198
199 vgl_vector_2d<double> v(-u.y(),u.x());
200 for (int iA=-int(nA_);iA<=(int)nA_;++iA)
201 {
202 double A = iA*dA_;
203 vgl_vector_2d<double> uA = u*std::cos(A)+v*std::sin(A);
204 add_one_example(image,p,uA);
205 }
206 }
207
208 //: Build this object from the data supplied in add_example()
build(mfpf_point_finder & pf)209 void mfpf_hog_box_finder_builder::build(mfpf_point_finder& pf)
210 {
211 assert(pf.is_a()=="mfpf_hog_box_finder");
212 auto& rp = static_cast<mfpf_hog_box_finder&>(pf);
213
214 mfpf_vec_cost *cost = cost_builder().new_cost();
215
216 cost_builder().build(*cost);
217
218 rp.set(nA_bins_,full360_,ni_,nj_,nc_,
219 ref_x_,ref_y_,*cost,normaliser_);
220 set_base_parameters(rp);
221 rp.set_overlap_f(overlap_f_);
222
223 std::cout<<"Model: "<<rp<<std::endl;
224
225 // Tidy up
226 delete cost;
227 }
228
229
230 //=======================================================================
231 // Method: set_from_stream
232 //=======================================================================
233 //: Initialise from a string stream
set_from_stream(std::istream & is)234 bool mfpf_hog_box_finder_builder::set_from_stream(std::istream &is)
235 {
236 // Cycle through string and produce a map of properties
237 std::string s = mbl_parse_block(is);
238 std::istringstream ss(s);
239 mbl_read_props_type props = mbl_read_props_ws(ss);
240
241 set_defaults();
242
243 // Extract the properties
244 parse_base_props(props);
245
246 nc_=vul_string_atoi(props.get_optional_property("nc","2"));
247 nA_bins_=vul_string_atoi(props.get_optional_property("nA_bins","8"));
248 full360_=vul_string_to_bool(props.get_optional_property("full360","true"));
249 ni_=vul_string_atoi(props.get_optional_property("ni","4"));
250 nj_=vul_string_atoi(props.get_optional_property("nj","4"));
251
252 bool reonfigureNormaliser=false;
253 if (props.find("norm")!=props.end())
254 {
255 std::istringstream ss2(props["norm"]);
256 mbl_read_props_type dummy_extra_props;
257 std::unique_ptr<mipa_vector_normaliser> norm = mipa_vector_normaliser::new_normaliser_from_stream(ss2, dummy_extra_props);
258 normaliser_=norm.release();
259 reonfigureNormaliser=true;
260 #if 0
261 if (props["norm"]=="none") norm_method_=0;
262 else
263 if (props["norm"]=="linear") norm_method_=1;
264 else throw mbl_exception_parse_error("Unknown norm: "+props["norm"]);
265 #endif
266
267 props.erase("norm");
268 }
269
270 overlap_f_=vul_string_atof(props.get_optional_property("overlap_f","1.0"));
271
272 if (props.find("ref_x")!=props.end())
273 {
274 ref_x_=vul_string_atof(props["ref_x"]);
275 props.erase("ref_x");
276 }
277 else ref_x_=0.5*(ni_-1);
278
279 if (props.find("ref_y")!=props.end())
280 {
281 ref_y_=vul_string_atof(props["ref_y"]);
282 props.erase("ref_y");
283 }
284 else ref_y_=0.5*(nj_-1);
285
286 if (props.find("nA")!=props.end())
287 {
288 nA_=vul_string_atoi(props["nA"]);
289 props.erase("nA");
290 }
291
292 if (props.find("dA")!=props.end())
293 {
294 dA_=vul_string_atof(props["dA"]);
295 props.erase("dA");
296 }
297
298 if (props.find("cost_builder")!=props.end())
299 {
300 std::istringstream b_ss(props["cost_builder"]);
301 std::unique_ptr<mfpf_vec_cost_builder> bb =
302 mfpf_vec_cost_builder::create_from_stream(b_ss);
303 cost_builder_ = bb->clone();
304 props.erase("cost_builder");
305 }
306
307 //Some classes of normaliser may require reconfiguration (e.g. to pass on the region size)
308 if (reonfigureNormaliser)
309 {
310 reconfigure_normaliser();
311 }
312 // Check for unused props
313 mbl_read_props_look_for_unused_props(
314 "mfpf_hog_box_finder_builder::set_from_stream", props, mbl_read_props_type());
315 return true;
316 }
317
reconfigure_normaliser()318 void mfpf_hog_box_finder_builder::reconfigure_normaliser()
319 {
320 mipa_vector_normaliser* pNormaliser=normaliser_.ptr();
321 auto* pBlockNormaliser= dynamic_cast<mipa_block_normaliser*>(pNormaliser);
322 if (pBlockNormaliser)
323 {
324 pBlockNormaliser->set_region(2*ni_,2*nj_);
325 pBlockNormaliser->set_nbins(nA_bins_);
326 //Also this builder always uses 2 SIFT scales and a final overall histogram
327 auto* pMSBlockNormaliser= dynamic_cast<mipa_ms_block_normaliser*>(pNormaliser);
328 if (pMSBlockNormaliser)
329 {
330 pMSBlockNormaliser->set_nscales(2);
331 pMSBlockNormaliser->set_include_overall_histogram(true);
332 }
333 else
334 {
335 std::cerr<<"WARNING from fpf_hog_box_finder_builder::reconfigure_normaliser...\n"
336 <<"The normaliser may not be multi-scale but this HOG Builder uses multi-scale histograms\n";
337 }
338 }
339 }
340
341 //=======================================================================
342 // Method: is_a
343 //=======================================================================
344
is_a() const345 std::string mfpf_hog_box_finder_builder::is_a() const
346 {
347 return std::string("mfpf_hog_box_finder_builder");
348 }
349
350 //: Create a copy on the heap and return base class pointer
clone() const351 mfpf_point_finder_builder* mfpf_hog_box_finder_builder::clone() const
352 {
353 return new mfpf_hog_box_finder_builder(*this);
354 }
355
356 //=======================================================================
357 // Method: print
358 //=======================================================================
359
print_summary(std::ostream & os) const360 void mfpf_hog_box_finder_builder::print_summary(std::ostream& os) const
361 {
362 os << "{ "<<'\n';
363 vsl_indent_inc(os);
364 os << vsl_indent()<<"size: " << ni_ << 'x' << nj_
365 << " nc: " << nc_ <<" nA_bins: "<<nA_bins_
366 << " ref_pt: (" << ref_x_ << ',' << ref_y_ << ')' <<'\n';
367 if (full360_) os<<vsl_indent()<<"Angle range: 0-360"<<'\n';
368 else os<<vsl_indent()<<"Angle range: 0-180"<<'\n';
369 std::cout<<"The HOG's normaliser is:"<<'\n';
370 normaliser_->print_summary(os);
371 //if (norm_method_==0) os<<vsl_indent()<<"norm: none"<<'\n';
372 //else os<<vsl_indent()<<"norm: linear"<<'\n';
373 os <<vsl_indent()<< "cost_builder: ";
374 if (cost_builder_.ptr()==nullptr) os << '-'<<'\n';
375 else os << cost_builder_<<'\n';
376 os <<vsl_indent()<< "nA: " << nA_ << " dA: " << dA_ << ' '<<'\n'
377 <<vsl_indent();
378 mfpf_point_finder_builder::print_summary(os);
379 os <<'\n'
380 <<vsl_indent()<<"overlap_f: "<<overlap_f_<<'\n';
381 vsl_indent_dec(os);
382 os <<vsl_indent()<< '}';
383 }
384
385 //: Version number for I/O
version_no() const386 short mfpf_hog_box_finder_builder::version_no() const
387 {
388 return 2;
389 }
390
b_write(vsl_b_ostream & bfs) const391 void mfpf_hog_box_finder_builder::b_write(vsl_b_ostream& bfs) const
392 {
393 vsl_b_write(bfs,version_no());
394 mfpf_point_finder_builder::b_write(bfs); // Save base class
395 vsl_b_write(bfs,nc_);
396 vsl_b_write(bfs,ni_);
397 vsl_b_write(bfs,nj_);
398 vsl_b_write(bfs,nA_bins_);
399 vsl_b_write(bfs,full360_);
400 vsl_b_write(bfs,ref_x_);
401 vsl_b_write(bfs,ref_y_);
402 vsl_b_write(bfs,nA_);
403 vsl_b_write(bfs,dA_);
404 vsl_b_write(bfs,cost_builder_);
405 vsl_b_write(bfs,normaliser_);
406 vsl_b_write(bfs,overlap_f_);
407 }
408
409 //=======================================================================
410 // Method: load
411 //=======================================================================
412
b_read(vsl_b_istream & bfs)413 void mfpf_hog_box_finder_builder::b_read(vsl_b_istream& bfs)
414 {
415 if (!bfs) return;
416 short version;
417 vsl_b_read(bfs,version);
418 switch (version)
419 {
420 case (1):
421 case (2):
422 mfpf_point_finder_builder::b_read(bfs); // Load base class
423 vsl_b_read(bfs,nc_);
424 vsl_b_read(bfs,ni_);
425 vsl_b_read(bfs,nj_);
426 vsl_b_read(bfs,nA_bins_);
427 vsl_b_read(bfs,full360_);
428 vsl_b_read(bfs,ref_x_);
429 vsl_b_read(bfs,ref_y_);
430 vsl_b_read(bfs,nA_);
431 vsl_b_read(bfs,dA_);
432 vsl_b_read(bfs,cost_builder_);
433 vsl_b_read(bfs,normaliser_);
434 if (version==1) overlap_f_=1.0;
435 else vsl_b_read(bfs,overlap_f_);
436 break;
437 default:
438 std::cerr << "I/O ERROR: vsl_b_read(vsl_b_istream&)\n"
439 << " Unknown version number "<< version << '\n';
440 bfs.is().clear(std::ios::badbit); // Set an unrecoverable IO error on stream
441 return;
442 }
443 }
444