1 /*
2  * Copyright (c) 2015, Piotr Dobrowolski dobrypd[at]gmail[dot]com
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without modification,
6  * are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions and the following disclaimer in the documentation
13  *    and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
24  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27 #include <cstdlib>
28 #include <cstdio>
29 #include <iostream>
30 #include <algorithm>
31 #include <opencv2/opencv.hpp>
32 
33 using namespace std;
34 using namespace cv;
35 
36 const char * windowOriginal = "Captured preview";
37 const int FOCUS_STEP = 1024;
38 const int MAX_FOCUS_STEP = 32767;
39 const int FOCUS_DIRECTION_INFTY = 1;
40 const int DEFAULT_BREAK_LIMIT = 5;
41 const int DEFAULT_OUTPUT_FPS = 20;
42 const double epsylon = 0.0005; // compression, noice, etc.
43 
44 struct Args_t
45 {
46     const char * deviceName;
47     const char * output;
48     unsigned int fps;
49     unsigned int minimumFocusStep;
50     unsigned int breakLimit;
51     bool measure;
52     bool verbose;
53 } GlobalArgs;
54 
55 struct FocusState
56 {
57     int step;
58     int direction;
59     int minFocusStep;
60     int lastDirectionChange;
61     int stepToLastMax;
62     double rate;
63     double rateMax;
64 };
65 
operator <<(ostream & os,FocusState & state)66 static ostream & operator<<(ostream & os, FocusState & state)
67 {
68     return os << "RATE=" << state.rate << "\tSTEP="
69             << state.step * state.direction << "\tLast change="
70             << state.lastDirectionChange << "\tstepToLastMax="
71             << state.stepToLastMax;
72 }
73 
createInitialState()74 static FocusState createInitialState()
75 {
76     FocusState state;
77     state.step = FOCUS_STEP;
78     state.direction = FOCUS_DIRECTION_INFTY;
79     state.minFocusStep = 0;
80     state.lastDirectionChange = 0;
81     state.stepToLastMax = 0;
82     state.rate = 0;
83     state.rateMax = 0;
84     return state;
85 }
86 
focusDriveEnd(VideoCapture & cap,int direction)87 static void focusDriveEnd(VideoCapture & cap, int direction)
88 {
89     while (cap.set(CAP_PROP_ZOOM, (double) MAX_FOCUS_STEP * direction))
90         ;
91 }
92 
93 /**
94  * Minimal focus step depends on lens
95  * and I don't want to make any assumptions about it.
96  */
findMinFocusStep(VideoCapture & cap,unsigned int startWith,int direction)97 static int findMinFocusStep(VideoCapture & cap, unsigned int startWith,
98         int direction)
99 {
100     int lStep, rStep;
101     lStep = 0;
102     rStep = startWith;
103 
104     focusDriveEnd(cap, direction * FOCUS_DIRECTION_INFTY);
105     while (lStep < rStep)
106     {
107         int mStep = (lStep + rStep) / 2;
108         cap.set(CAP_PROP_ZOOM, direction * FOCUS_DIRECTION_INFTY * FOCUS_STEP);
109         if (cap.set(CAP_PROP_ZOOM, -direction * mStep))
110         {
111             rStep = mStep;
112         }
113         else
114         {
115             lStep = mStep + 1;
116         }
117     }
118     cap.set(CAP_PROP_ZOOM, direction * FOCUS_DIRECTION_INFTY * MAX_FOCUS_STEP);
119     if (GlobalArgs.verbose)
120     {
121         cout << "Found minimal focus step = " << lStep << endl;
122     }
123     return lStep;
124 }
125 
126 /**
127  * Rate frame from 0/blury/ to 1/sharp/.
128  */
rateFrame(Mat & frame)129 static double rateFrame(Mat & frame)
130 {
131     unsigned long int sum = 0;
132     unsigned long int size = frame.cols * frame.rows;
133     Mat edges;
134     cvtColor(frame, edges, CV_BGR2GRAY);
135     GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
136     Canny(edges, edges, 0, 30, 3);
137 
138     MatIterator_<uchar> it, end;
139     for (it = edges.begin<uchar>(), end = edges.end<uchar>(); it != end; ++it)
140     {
141         sum += *it != 0;
142     }
143 
144     return (double) sum / (double) size;
145 }
146 
correctFocus(bool lastSucceeded,FocusState & state,double rate)147 static int correctFocus(bool lastSucceeded, FocusState & state, double rate)
148 {
149     if (GlobalArgs.verbose)
150     {
151         cout << "RATE=" << rate << endl;
152     }
153     state.lastDirectionChange++;
154     double rateDelta = rate - state.rate;
155 
156     if (rate >= state.rateMax + epsylon)
157     {
158         // Update Max
159         state.stepToLastMax = 0;
160         state.rateMax = rate;
161         // My local minimum is now on the other direction, that's why:
162         state.lastDirectionChange = 0;
163     }
164 
165     if (!lastSucceeded)
166     {
167         // Focus at limit or other problem, change the direction.
168         state.direction *= -1;
169         state.lastDirectionChange = 0;
170         state.step /= 2;
171     }
172     else
173     {
174         if (rate < epsylon)
175         { // It's hard to say anything
176             state.step = FOCUS_STEP;
177         }
178         else if (rateDelta < -epsylon)
179         { // Wrong direction ?
180             state.direction *= -1;
181             state.step = static_cast<int>(static_cast<double>(state.step) * 0.75);
182             state.lastDirectionChange = 0;
183         }
184         else if ((rate + epsylon < state.rateMax)
185                 && ((state.lastDirectionChange > 3)
186                         || ((state.step < (state.minFocusStep * 1.5))
187                                 && state.stepToLastMax > state.step)))
188         { // I've done 3 steps (or I'm finishing) without improvement, go back to max.
189             state.direction = state.stepToLastMax >= 0 ? 1 : -1;
190             state.step = static_cast<int>(static_cast<double>(state.step) * 0.75);
191             int stepToMax = abs(state.stepToLastMax);
192             state.stepToLastMax = 0;
193             state.lastDirectionChange = 0; // Like reset.
194             state.rate = rate;
195             return stepToMax;
196         }
197     }
198     // Update state.
199     state.rate = rate;
200     state.stepToLastMax -= state.direction * state.step;
201     return state.step;
202 }
203 
showHelp(const char * pName,bool welcomeMsg)204 static void showHelp(const char * pName, bool welcomeMsg)
205 {
206     cout << "This program demonstrates usage of gPhoto2 VideoCapture.\n\n"
207             "With OpenCV build without gPhoto2 library support it will "
208             "do nothing special, just capture.\n\n"
209             "Simple implementation of autofocus is based on edges detection.\n"
210             "It was tested (this example) only with Nikon DSLR (Nikon D90).\n"
211             "But shall work on all Nikon DSLRs, and with little effort with other devices.\n"
212             "Visit http://www.gphoto.org/proj/libgphoto2/support.php\n"
213             "to find supported devices (need Image Capture at least).\n"
214             "Before run, set your camera autofocus ON.\n\n";
215 
216     if (!welcomeMsg)
217     {
218         cout << "usage " << pName << ": [OPTIONS] DEVICE_NAME\n\n"
219                 "OPTIONS:\n"
220                 "\t-h\t\treturns this help message,\n"
221                 "\t-o FILENAME\tsave output video in file (MJPEG only),\n"
222                 "\t-f FPS\t\tframes per second in output video,\n"
223                 "\t-m\t\tmeasure exposition\n"
224                 "\t\t\t(returns rates from closest focus to INTY\n"
225                 "\t\t\tfor every minimum step),\n"
226                 "\t-d INT\t\tset minimum focus step,\n"
227                 "\t-v\t\tverbose mode.\n\n\n"
228                 "DEVICE_NAME\t\tis your digital camera model substring.\n\n\n"
229                 "On runtime you can use keys to control:\n";
230     }
231     else
232     {
233         cout << "Actions:\n";
234     }
235 
236     cout << "\tk:\t- focus out,\n"
237             "\tj:\t- focus in,\n"
238             "\t,:\t- focus to the closest point,\n"
239             "\t.:\t- focus to infinity,\n"
240             "\tr:\t- reset autofocus state,\n"
241             "\tf:\t- switch autofocus on/off,\n"
242             "\tq:\t- quit.\n";
243 }
244 
parseArguments(int argc,char ** argv)245 static bool parseArguments(int argc, char ** argv)
246 {
247     int index;
248     GlobalArgs.deviceName = "Nikon";
249     GlobalArgs.output = NULL;
250     GlobalArgs.fps = DEFAULT_OUTPUT_FPS;
251     GlobalArgs.minimumFocusStep = 0;
252     GlobalArgs.breakLimit = DEFAULT_BREAK_LIMIT;
253     GlobalArgs.measure = false;
254     GlobalArgs.verbose = false;
255 
256     for (index = 1; index < argc; index++)
257     {
258         const char * arg = argv[index];
259         if (strcmp(arg, "-h") == 0)
260         {
261             return false;
262         }
263         else if (strcmp(arg, "-o") == 0)
264         {
265             GlobalArgs.output = argv[++index];
266         }
267         else if (strcmp(arg, "-f") == 0)
268         {
269             if (sscanf(argv[++index], "%u", &GlobalArgs.fps) != 1
270                     || GlobalArgs.fps <= 0)
271             {
272                 cerr << "Invalid fps argument." << endl;
273                 return false;
274             }
275         }
276         else if (strcmp(arg, "-m") == 0)
277         {
278             GlobalArgs.measure = true;
279         }
280         else if (strcmp(arg, "-v") == 0)
281         {
282             GlobalArgs.verbose = true;
283         }
284         else if (strcmp(arg, "-d") == 0)
285         {
286             if (sscanf(argv[++index], "%u", &GlobalArgs.minimumFocusStep) != 1
287                     || GlobalArgs.minimumFocusStep <= 0)
288             {
289                 cerr << "Invalid minimum focus step argument." << endl;
290                 return false;
291             }
292         }
293         else if (arg[0] != '-')
294         {
295             GlobalArgs.deviceName = arg;
296         }
297         else
298         {
299             cerr << "Unknown option " << arg << endl;
300         }
301     }
302     return true;
303 }
304 
main(int argc,char ** argv)305 int main(int argc, char ** argv)
306 {
307     if (!parseArguments(argc, argv))
308     {
309         showHelp(argv[0], false);
310         return -1;
311     }
312     VideoCapture cap(GlobalArgs.deviceName);
313     if (!cap.isOpened())
314     {
315         cout << "Cannot find device " << GlobalArgs.deviceName << endl;
316         showHelp(argv[0], false);
317         return -1;
318     }
319 
320     VideoWriter videoWriter;
321     Mat frame;
322     FocusState state = createInitialState();
323     bool focus = true;
324     bool lastSucceeded = true;
325     namedWindow(windowOriginal, 1);
326 
327     // Get settings:
328     if (GlobalArgs.verbose)
329     {
330         if ((cap.get(CAP_PROP_GPHOTO2_WIDGET_ENUMERATE) == 0)
331                 || (cap.get(CAP_PROP_GPHOTO2_WIDGET_ENUMERATE) == -1))
332         {
333             // Some VideoCapture implementations can return -1, 0.
334             cout << "This is not GPHOTO2 device." << endl;
335             return -2;
336         }
337         cout << "List of camera settings: " << endl
338                 << (const char *) (intptr_t) cap.get(CAP_PROP_GPHOTO2_WIDGET_ENUMERATE)
339                 << endl;
340         cap.set(CAP_PROP_GPHOTO2_COLLECT_MSGS, true);
341     }
342 
343     cap.set(CAP_PROP_GPHOTO2_PREVIEW, true);
344     cap.set(CAP_PROP_VIEWFINDER, true);
345     cap >> frame; // To check PREVIEW output Size.
346     if (GlobalArgs.output != NULL)
347     {
348         Size S = Size((int) cap.get(CAP_PROP_FRAME_WIDTH), (int) cap.get(CAP_PROP_FRAME_HEIGHT));
349         int fourCC = CV_FOURCC('M', 'J', 'P', 'G');
350         videoWriter.open(GlobalArgs.output, fourCC, GlobalArgs.fps, S, true);
351         if (!videoWriter.isOpened())
352         {
353             cerr << "Cannot open output file " << GlobalArgs.output << endl;
354             showHelp(argv[0], false);
355             return -1;
356         }
357     }
358     showHelp(argv[0], true); // welcome msg
359 
360     if (GlobalArgs.minimumFocusStep == 0)
361     {
362         state.minFocusStep = findMinFocusStep(cap, FOCUS_STEP / 16, -FOCUS_DIRECTION_INFTY);
363     }
364     else
365     {
366         state.minFocusStep = GlobalArgs.minimumFocusStep;
367     }
368     focusDriveEnd(cap, -FOCUS_DIRECTION_INFTY); // Start with closest
369 
370     char key = 0;
371     while (key != 'q' && key != 27 /*ESC*/)
372     {
373         cap >> frame;
374         if (frame.empty())
375         {
376             break;
377         }
378         if (GlobalArgs.output != NULL)
379         {
380             videoWriter << frame;
381         }
382 
383         if (focus && !GlobalArgs.measure)
384         {
385             int stepToCorrect = correctFocus(lastSucceeded, state, rateFrame(frame));
386             lastSucceeded = cap.set(CAP_PROP_ZOOM,
387                     max(stepToCorrect, state.minFocusStep) * state.direction);
388             if ((!lastSucceeded) || (stepToCorrect < state.minFocusStep))
389             {
390                 if (--GlobalArgs.breakLimit <= 0)
391                 {
392                     focus = false;
393                     state.step = state.minFocusStep * 4;
394                     cout << "In focus, you can press 'f' to improve with small step, "
395                             "or 'r' to reset." << endl;
396                 }
397             }
398             else
399             {
400                 GlobalArgs.breakLimit = DEFAULT_BREAK_LIMIT;
401             }
402         }
403         else if (GlobalArgs.measure)
404         {
405             double rate = rateFrame(frame);
406             if (!cap.set(CAP_PROP_ZOOM, state.minFocusStep))
407             {
408                 if (--GlobalArgs.breakLimit <= 0)
409                 {
410                     break;
411                 }
412             }
413             else
414             {
415                 cout << rate << endl;
416             }
417         }
418 
419         if ((focus || GlobalArgs.measure) && GlobalArgs.verbose)
420         {
421             cout << "STATE\t" << state << endl;
422             cout << "Output from camera: " << endl
423                     << (const char *) (intptr_t) cap.get(CAP_PROP_GPHOTO2_FLUSH_MSGS) << endl;
424         }
425 
426         imshow(windowOriginal, frame);
427         switch (key = static_cast<char>(waitKey(30)))
428         {
429             case 'k': // focus out
430                 cap.set(CAP_PROP_ZOOM, 100);
431                 break;
432             case 'j': // focus in
433                 cap.set(CAP_PROP_ZOOM, -100);
434                 break;
435             case ',': // Drive to closest
436                 focusDriveEnd(cap, -FOCUS_DIRECTION_INFTY);
437                 break;
438             case '.': // Drive to infinity
439                 focusDriveEnd(cap, FOCUS_DIRECTION_INFTY);
440                 break;
441             case 'r': // reset focus state
442                 focus = true;
443                 state = createInitialState();
444                 break;
445             case 'f': // focus switch on/off
446                 focus ^= true;
447                 break;
448         }
449     }
450 
451     if (GlobalArgs.verbose)
452     {
453         cout << "Captured " << (int) cap.get(CAP_PROP_FRAME_COUNT) << " frames"
454                 << endl << "in " << (int) (cap.get(CAP_PROP_POS_MSEC) / 1e2)
455                 << " seconds," << endl << "at avg speed "
456                 << (cap.get(CAP_PROP_FPS)) << " fps." << endl;
457     }
458 
459     return 0;
460 }
461