项目作者: ducha-aiki

项目描述 :
MODS with external deep descriptors/detectors
高级语言: C++
项目地址: git://github.com/ducha-aiki/mods-light-zmq.git
创建时间: 2018-05-07T13:44:31Z
项目社区:https://github.com/ducha-aiki/mods-light-zmq

开源协议:GNU General Public License v2.0

下载


MODS with external deep learning components

This is MODS version, which allows you using state-of-the-art deep descriptors like HardNet without linking MODS to any of deep learning library.
It contains very small number of detectors and descriptors implemented inside — for easier compilation.
Instead it uses zeromq library for communication with separately run CNN daemons.
Examples with python PyTorch AffNet and HardNet++ descriptors is provided, but you can use any language and any DL package you like, just modify corresponding scripts.

How to compile MODS

I expect, that you have already installed latest PyTorch (0.5)

  1. cd build
  2. cmake ..
  3. make

Image matching example

Relevant config-files are: config_aff_ori_desc_zeromq.ini and iters_HessianZMQ.ini
With Hessian-AffNet, OriNet and HardNet++

  1. ./run_zmq_servers.sh

Wait until initialization on GPU is done and you see:

  1. Extracting on GPU
  2. Extracting on GPU
  3. Extracting on GPU

Now you can run matching:

  1. ./mods imgs/graf1.png imgs/graf6.png out1_deep.jpg out2_deep.jpg k1.txt k2.txt m.txt l.log 0 0 abcd.txt config_aff_ori_desc_zeromq.ini iters_HessianZMQ.ini

Expected output:

  1. Maximum threads can be used: 4
  2. View synthesis, detection and description...
  3. Iteration 0
  4. HessianAffine: 1 synthesis will be done.
  5. ('processing', 0.07718610763549805, 1.6556436644250974e-05, ' per patch')
  6. ('processing', 0.061591148376464844, 1.6110684900984786e-05, ' per patch')
  7. ('processing', 0.07169699668884277, 1.5837640090312078e-05, ' per patch')
  8. ('processing', 0.05922508239746094, 1.5873782470506817e-05, ' per patch')
  9. ('processing', 0.1312699317932129, 3.1877108254787004e-05, ' per patch')
  10. ('processing', 0.10080504417419434, 3.0019369914888128e-05, ' per patch')
  11. Matching ...
  12. Matching ...
  13. 3358 4118
  14. 264 tentatives found.
  15. Duplicate filtering before RANSAC with threshold = 2 pixels.
  16. 254 unique tentatives left
  17. LO-RANSAC(homography) verification is used...
  18. 147 RANSAC correspondences got
  19. 147 true matches are identified in 0.003 seconds
  20. Done in 1 iterations
  21. *********************
  22. Writing files...
  23. HessianAffine 2
  24. HessianAffine 2
  25. Writing images with matches... done
  26. Image1: regions descriptors | Image2: regions descriptors
  27. 3731 3358 | 4527 4118
  28. True matches | unique tentatives
  29. 147 | 254 | 57.9% 1st geom inc
  30. Main matching | All Time:
  31. 2.02 | 2.52 seconds
  32. Timings: (sec/%)
  33. Synth|Detect|Orient|Desc|Match|RANSAC|MISC|Total
  34. 0.011 0.721 0.568 0.463 0.229 0.003 0.527 2.52
  35. 0.438 28.6 22.5 18.4 9.08 0.119 20.9 100

Don`t forget to kill server process after work done.

Now run with classical HessianAffine(Baumberg) + RootSIFT:

  1. ./mods imgs/graf1.png imgs/graf6.png out1_classic.jpg out2_classic.jpg k1.txt k2.txt m.txt l.log 0 0 abcd.txt config_affori_classic.ini iters_HessianSIFT.ini

Relevant config-files are: config_affori_classic.ini and iters_HessianSIFT.ini

Expected output:

  1. Maximum threads can be used: 4
  2. View synthesis, detection and description...
  3. Iteration 0
  4. HessianAffine: 1 synthesis will be done.
  5. Matching ...
  6. Matching ...
  7. 2331 2912
  8. 76 tentatives found.
  9. Duplicate filtering before RANSAC with threshold = 2 pixels.
  10. 74 unique tentatives left
  11. LO-RANSAC(homography) verification is used...
  12. 21 RANSAC correspondences got
  13. 21 true matches are identified in 0.002 seconds
  14. Done in 1 iterations
  15. *********************
  16. Writing files...
  17. HessianAffine 2
  18. HessianAffine 2
  19. Writing images with matches... done
  20. Image1: regions descriptors | Image2: regions descriptors
  21. 2665 2331 | 3287 2912
  22. True matches | unique tentatives
  23. 21 | 74 | 28.4% 1st geom inc
  24. Main matching | All Time:
  25. 0.915 | 1.25 seconds
  26. Timings: (sec/%)
  27. Synth|Detect|Orient|Desc|Match|RANSAC|MISC|Total
  28. 0.0106 0.183 0.0771 0.439 0.169 0.002 0.37 1.25
  29. 0.85 14.6 6.16 35.1 13.5 0.16 29.6 100

As you can see, deep descriptors are much better, although slower
If you need to match really hard pairs, use iters_MODS_ZMQ.ini config file, or write your own configuation for view synthesis.

If you need to extract and save features from directory with images:

Generate two text files, one with paths to the input images (one path per line) and one with output path for features. Then run extract_features_batch util.

  1. find imgs/* -type f > imgs_to_extract_list.txt
  2. mkdir output_features
  3. python get_output_fnames.py imgs_to_extract_list.txt output_features extracted_features_fnames.txt
  4. ./run_zmq_servers.sh
  5. ./extract_features_batch imgs_to_extract_list.txt extracted_features_fnames.txt config_aff_ori_desc_zeromq.ini iters_HessianZMQ.ini

Extracted features will be in output_features directory, in OxAff-like format: x y a b c desc[128]

Descriptor daemon script structure

It is simple python(might be any other language) script with following three main parts.
See desc_server.py for example.

1)zeromq socket initialization:

  1. context = zmq.Context()
  2. socket = context.socket(zmq.REP)
  3. socket.bind("tcp://*:" + args.port)

port number should be the same, as listening port in corresponding section of config_aff_ori_desc_zeromq.ini file:

  1. [zmqDescriptor]
  2. port=tcp://localhost:5555
  3. patchSize=32; width and height of the patch
  4. mrSize=5.1962 ;

2)Waiting for input patches. Patches come as grayscale uint8 png image with size (ps * n_patches, ps), where ps is set in config_aff_ori_desc_zeromq.ini

  1. while True:
  2. # Wait for next request from client
  3. message = socket.recv()
  4. img = decode_msg(message).astype(np.float32)

3)Getting descriptors and sending them back, as numpy float32 (num_patches,desc_dim) array.

  1. descr = describe_patches(model, img, args.cuda, DESCR_OUT_DIM).astype(np.float32)
  2. buff = np.getbuffer(descr)
  3. socket.send(buff)

Saving in .npz format

Now you can save keypoints in .npz format. To do this, just pass k1.npz instead k1.txt in command line.
It will create .npz file with keys “xy”, “responses”, “scales”, “A”, “descs”.

https://github.com/ducha-aiki/mods-light-zmq/blob/master/imagerepresentation.cpp#L1266

Powered by great library https://github.com/rogersce/cnpy/

Citation

Please cite us if you use this code:

  1. @article{Mishkin2015MODS,
  2. title = "MODS: Fast and robust method for two-view matching ",
  3. journal = "Computer Vision and Image Understanding ",
  4. year = "2015",
  5. issn = "1077-3142",
  6. doi = "http://dx.doi.org/10.1016/j.cviu.2015.08.005",
  7. url = "http://www.sciencedirect.com/science/article/pii/S1077314215001800",
  8. author = "Dmytro Mishkin and Jiri Matas and Michal Perdoch"
  9. }

And if you use provided deep descriptors, please cite:

  1. @article{HardNet2017,
  2. author = {Anastasiya Mishchuk, Dmytro Mishkin, Filip Radenovic, Jiri Matas},
  3. title = "{Working hard to know your neighbor's margins: Local descriptor learning loss}",
  4. booktitle = {Proceedings of NIPS},
  5. year = 2017,
  6. month = dec}
  7. @article{AffNet2017,
  8. author = {Dmytro Mishkin, Filip Radenovic, Jiri Matas},
  9. title = "{Learning Discriminative Affine Regions via Discriminability}",
  10. year = 2017,
  11. month = nov}