123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- /*
- * Copyright (c) 2015, Piotr Dobrowolski dobrypd[at]gmail[dot]com
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
- #include <cstdlib>
- #include <cstdio>
- #include <iostream>
- #include <algorithm>
- #include <opencv2/core.hpp>
- #include <opencv2/imgproc.hpp>
- #include <opencv2/highgui.hpp>
- using namespace std;
- using namespace cv;
- const char * windowOriginal = "Captured preview";
- const int FOCUS_STEP = 1024;
- const int MAX_FOCUS_STEP = 32767;
- const int FOCUS_DIRECTION_INFTY = 1;
- const int DEFAULT_BREAK_LIMIT = 5;
- const int DEFAULT_OUTPUT_FPS = 20;
- const double epsylon = 0.0005; // compression, noise, etc.
- struct Args_t
- {
- string deviceName;
- string output;
- int fps;
- int minimumFocusStep;
- int breakLimit;
- bool measure;
- bool verbose;
- } GlobalArgs;
- struct FocusState
- {
- int step;
- int direction;
- int minFocusStep;
- int lastDirectionChange;
- int stepToLastMax;
- double rate;
- double rateMax;
- };
- static ostream & operator<<(ostream & os, FocusState & state)
- {
- return os << "RATE=" << state.rate << "\tSTEP="
- << state.step * state.direction << "\tLast change="
- << state.lastDirectionChange << "\tstepToLastMax="
- << state.stepToLastMax;
- }
- static FocusState createInitialState()
- {
- FocusState state;
- state.step = FOCUS_STEP;
- state.direction = FOCUS_DIRECTION_INFTY;
- state.minFocusStep = 0;
- state.lastDirectionChange = 0;
- state.stepToLastMax = 0;
- state.rate = 0;
- state.rateMax = 0;
- return state;
- }
- static void focusDriveEnd(VideoCapture & cap, int direction)
- {
- while (cap.set(CAP_PROP_ZOOM, (double) MAX_FOCUS_STEP * direction))
- ;
- }
- /**
- * Minimal focus step depends on lens
- * and I don't want to make any assumptions about it.
- */
- static int findMinFocusStep(VideoCapture & cap, unsigned int startWith,
- int direction)
- {
- int lStep, rStep;
- lStep = 0;
- rStep = startWith;
- focusDriveEnd(cap, direction * FOCUS_DIRECTION_INFTY);
- while (lStep < rStep)
- {
- int mStep = (lStep + rStep) / 2;
- cap.set(CAP_PROP_ZOOM, direction * FOCUS_DIRECTION_INFTY * FOCUS_STEP);
- if (cap.set(CAP_PROP_ZOOM, -direction * mStep))
- {
- rStep = mStep;
- }
- else
- {
- lStep = mStep + 1;
- }
- }
- cap.set(CAP_PROP_ZOOM, direction * FOCUS_DIRECTION_INFTY * MAX_FOCUS_STEP);
- if (GlobalArgs.verbose)
- {
- cout << "Found minimal focus step = " << lStep << endl;
- }
- return lStep;
- }
- /**
- * Rate frame from 0/blury/ to 1/sharp/.
- */
- static double rateFrame(Mat & frame)
- {
- unsigned long int sum = 0;
- unsigned long int size = frame.cols * frame.rows;
- Mat edges;
- cvtColor(frame, edges, COLOR_BGR2GRAY);
- GaussianBlur(edges, edges, Size(7, 7), 1.5, 1.5);
- Canny(edges, edges, 0, 30, 3);
- MatIterator_<uchar> it, end;
- for (it = edges.begin<uchar>(), end = edges.end<uchar>(); it != end; ++it)
- {
- sum += *it != 0;
- }
- return (double) sum / (double) size;
- }
- static int correctFocus(bool lastSucceeded, FocusState & state, double rate)
- {
- if (GlobalArgs.verbose)
- {
- cout << "RATE=" << rate << endl;
- }
- state.lastDirectionChange++;
- double rateDelta = rate - state.rate;
- if (rate >= state.rateMax + epsylon)
- {
- // Update Max
- state.stepToLastMax = 0;
- state.rateMax = rate;
- // My local minimum is now on the other direction, that's why:
- state.lastDirectionChange = 0;
- }
- if (!lastSucceeded)
- {
- // Focus at limit or other problem, change the direction.
- state.direction *= -1;
- state.lastDirectionChange = 0;
- state.step /= 2;
- }
- else
- {
- if (rate < epsylon)
- { // It's hard to say anything
- state.step = FOCUS_STEP;
- }
- else if (rateDelta < -epsylon)
- { // Wrong direction ?
- state.direction *= -1;
- state.step = static_cast<int>(static_cast<double>(state.step) * 0.75);
- state.lastDirectionChange = 0;
- }
- else if ((rate + epsylon < state.rateMax)
- && ((state.lastDirectionChange > 3)
- || ((state.step < (state.minFocusStep * 1.5))
- && state.stepToLastMax > state.step)))
- { // I've done 3 steps (or I'm finishing) without improvement, go back to max.
- state.direction = state.stepToLastMax >= 0 ? 1 : -1;
- state.step = static_cast<int>(static_cast<double>(state.step) * 0.75);
- int stepToMax = abs(state.stepToLastMax);
- state.stepToLastMax = 0;
- state.lastDirectionChange = 0; // Like reset.
- state.rate = rate;
- return stepToMax;
- }
- }
- // Update state.
- state.rate = rate;
- state.stepToLastMax -= state.direction * state.step;
- return state.step;
- }
- static void showHelp(const char * pName, bool welcomeMsg)
- {
- cout << "This program demonstrates usage of gPhoto2 VideoCapture.\n\n"
- "With OpenCV build without gPhoto2 library support it will "
- "do nothing special, just capture.\n\n"
- "Simple implementation of autofocus is based on edges detection.\n"
- "It was tested (this example) only with Nikon DSLR (Nikon D90).\n"
- "But shall work on all Nikon DSLRs, and with little effort with other devices.\n"
- "Visit http://www.gphoto.org/proj/libgphoto2/support.php\n"
- "to find supported devices (need Image Capture at least).\n"
- "Before run, set your camera autofocus ON.\n\n";
- if (!welcomeMsg)
- {
- cout << "usage " << pName << ": [OPTIONS] DEVICE_NAME\n\n"
- "OPTIONS:\n"
- "\t-h\t\treturns this help message,\n"
- "\t-o=<FILENAME>\tsave output video in file (MJPEG only),\n"
- "\t-f=FPS\t\tframes per second in output video,\n"
- "\t-m\t\tmeasure exposition\n"
- "\t\t\t(returns rates from closest focus to INTY\n"
- "\t\t\tfor every minimum step),\n"
- "\t-d=<INT>\t\tset minimum focus step,\n"
- "\t-v\t\tverbose mode.\n\n\n"
- "DEVICE_NAME\t\tis your digital camera model substring.\n\n\n"
- "On runtime you can use keys to control:\n";
- }
- else
- {
- cout << "Actions:\n";
- }
- cout << "\tk:\t- focus out,\n"
- "\tj:\t- focus in,\n"
- "\t,:\t- focus to the closest point,\n"
- "\t.:\t- focus to infinity,\n"
- "\tr:\t- reset autofocus state,\n"
- "\tf:\t- switch autofocus on/off,\n"
- "\tq:\t- quit.\n";
- }
- static bool parseArguments(int argc, char ** argv)
- {
- cv::CommandLineParser parser(argc, argv, "{h help ||}{o||}{f||}{m||}{d|0|}{v||}{@device|Nikon|}");
- if (parser.has("help"))
- return false;
- GlobalArgs.breakLimit = DEFAULT_BREAK_LIMIT;
- if (parser.has("o"))
- GlobalArgs.output = parser.get<string>("o");
- else
- GlobalArgs.output = "";
- if (parser.has("f"))
- GlobalArgs.fps = parser.get<int>("f");
- else
- GlobalArgs.fps = DEFAULT_OUTPUT_FPS;
- GlobalArgs.measure = parser.has("m");
- GlobalArgs.verbose = parser.has("v");
- GlobalArgs.minimumFocusStep = parser.get<int>("d");
- GlobalArgs.deviceName = parser.get<string>("@device");
- if (!parser.check())
- {
- parser.printErrors();
- return false;
- }
- if (GlobalArgs.fps < 0)
- {
- cerr << "Invalid fps argument." << endl;
- return false;
- }
- if (GlobalArgs.minimumFocusStep < 0)
- {
- cerr << "Invalid minimum focus step argument." << endl;
- return false;
- }
- return true;
- }
- int main(int argc, char ** argv)
- {
- if (!parseArguments(argc, argv))
- {
- showHelp(argv[0], false);
- return -1;
- }
- VideoCapture cap(GlobalArgs.deviceName);
- if (!cap.isOpened())
- {
- cout << "Cannot find device " << GlobalArgs.deviceName << endl;
- showHelp(argv[0], false);
- return -1;
- }
- VideoWriter videoWriter;
- Mat frame;
- FocusState state = createInitialState();
- bool focus = true;
- bool lastSucceeded = true;
- namedWindow(windowOriginal, 1);
- // Get settings:
- if (GlobalArgs.verbose)
- {
- if ((cap.get(CAP_PROP_GPHOTO2_WIDGET_ENUMERATE) == 0)
- || (cap.get(CAP_PROP_GPHOTO2_WIDGET_ENUMERATE) == -1))
- {
- // Some VideoCapture implementations can return -1, 0.
- cout << "This is not GPHOTO2 device." << endl;
- return -2;
- }
- cout << "List of camera settings: " << endl
- << (const char *) (intptr_t) cap.get(CAP_PROP_GPHOTO2_WIDGET_ENUMERATE)
- << endl;
- cap.set(CAP_PROP_GPHOTO2_COLLECT_MSGS, true);
- }
- cap.set(CAP_PROP_GPHOTO2_PREVIEW, true);
- cap.set(CAP_PROP_VIEWFINDER, true);
- cap >> frame; // To check PREVIEW output Size.
- if (!GlobalArgs.output.empty())
- {
- Size S = Size((int) cap.get(CAP_PROP_FRAME_WIDTH), (int) cap.get(CAP_PROP_FRAME_HEIGHT));
- int fourCC = VideoWriter::fourcc('M', 'J', 'P', 'G');
- videoWriter.open(GlobalArgs.output, fourCC, GlobalArgs.fps, S, true);
- if (!videoWriter.isOpened())
- {
- cerr << "Cannot open output file " << GlobalArgs.output << endl;
- showHelp(argv[0], false);
- return -1;
- }
- }
- showHelp(argv[0], true); // welcome msg
- if (GlobalArgs.minimumFocusStep == 0)
- {
- state.minFocusStep = findMinFocusStep(cap, FOCUS_STEP / 16, -FOCUS_DIRECTION_INFTY);
- }
- else
- {
- state.minFocusStep = GlobalArgs.minimumFocusStep;
- }
- focusDriveEnd(cap, -FOCUS_DIRECTION_INFTY); // Start with closest
- char key = 0;
- while (key != 'q' && key != 27 /*ESC*/)
- {
- cap >> frame;
- if (frame.empty())
- {
- break;
- }
- if (!GlobalArgs.output.empty())
- {
- videoWriter << frame;
- }
- if (focus && !GlobalArgs.measure)
- {
- int stepToCorrect = correctFocus(lastSucceeded, state, rateFrame(frame));
- lastSucceeded = cap.set(CAP_PROP_ZOOM,
- max(stepToCorrect, state.minFocusStep) * state.direction);
- if ((!lastSucceeded) || (stepToCorrect < state.minFocusStep))
- {
- if (--GlobalArgs.breakLimit <= 0)
- {
- focus = false;
- state.step = state.minFocusStep * 4;
- cout << "In focus, you can press 'f' to improve with small step, "
- "or 'r' to reset." << endl;
- }
- }
- else
- {
- GlobalArgs.breakLimit = DEFAULT_BREAK_LIMIT;
- }
- }
- else if (GlobalArgs.measure)
- {
- double rate = rateFrame(frame);
- if (!cap.set(CAP_PROP_ZOOM, state.minFocusStep))
- {
- if (--GlobalArgs.breakLimit <= 0)
- {
- break;
- }
- }
- else
- {
- cout << rate << endl;
- }
- }
- if ((focus || GlobalArgs.measure) && GlobalArgs.verbose)
- {
- cout << "STATE\t" << state << endl;
- cout << "Output from camera: " << endl
- << (const char *) (intptr_t) cap.get(CAP_PROP_GPHOTO2_FLUSH_MSGS) << endl;
- }
- imshow(windowOriginal, frame);
- switch (key = static_cast<char>(waitKey(30)))
- {
- case 'k': // focus out
- cap.set(CAP_PROP_ZOOM, 100);
- break;
- case 'j': // focus in
- cap.set(CAP_PROP_ZOOM, -100);
- break;
- case ',': // Drive to closest
- focusDriveEnd(cap, -FOCUS_DIRECTION_INFTY);
- break;
- case '.': // Drive to infinity
- focusDriveEnd(cap, FOCUS_DIRECTION_INFTY);
- break;
- case 'r': // reset focus state
- focus = true;
- state = createInitialState();
- break;
- case 'f': // focus switch on/off
- focus ^= true;
- break;
- }
- }
- if (GlobalArgs.verbose)
- {
- cout << "Captured " << (int) cap.get(CAP_PROP_FRAME_COUNT) << " frames"
- << endl << "in " << (int) (cap.get(CAP_PROP_POS_MSEC) / 1e2)
- << " seconds," << endl << "at avg speed "
- << (cap.get(CAP_PROP_FPS)) << " fps." << endl;
- }
- return 0;
- }
|