2 Copyright (C) 2012 Intel Corporation
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 #include "opencvluxplugin.h"
20 #include "timestamp.h"
21 #include <listplusplus.h>
22 #include <superptr.hpp>
23 #include <ambplugin.h>
27 #include <opencv2/opencv.hpp>
28 #include <opencv2/core/ocl.hpp>
31 #include <QFutureWatcher>
32 #include <QtConcurrent/QtConcurrent>
35 #include <opencv2/core/ocl.hpp>
39 const std::string VideoLogging = "VideoLogging";
40 const std::string DriverDrowsiness = "DriverDrowsiness";
41 const std::string OpenCL = "OpenCL";
45 extern "C" void create(AbstractRoutingEngine* routingengine, map<string, string> config)
47 auto plugin = new AmbPlugin<OpenCvLuxPlugin>(routingengine, config);
51 OpenCvLuxPlugin::OpenCvLuxPlugin(AbstractRoutingEngine* re, map<string, string> config, AbstractSource &parent)
52 :AmbPluginImpl(re, config, parent), lastLux(0), speed(0), latitude(0), longitude(0)
54 videoLogging = addPropertySupport(Zone::None, [](){
55 return new BasicPropertyType<bool>(VideoLogging, false);
58 driverDrowsiness = addPropertySupport(Zone::None, [](){
59 return new OpenCvLuxPlugin::DriverDrowsinessType(DriverDrowsiness, false);
62 openCl = addPropertySupport(Zone::None, [](){
63 return new BasicPropertyType<bool>(OpenCL, false);
66 extBrightness = addPropertySupport<VehicleProperty::ExteriorBrightnessType>(Zone::None);
68 shared = amb::make_unique(new Shared);
69 shared->parent = this;
71 shared->m_capture = NULL;
72 shared->mWriter = nullptr;
74 shared->threaded = false;
75 shared->kinect = false;
76 shared->useCuda = false;
77 shared->loggingOn = false;
82 shared->pixelLowerBound = 0;
83 shared->pixelUpperBound = 255;
86 if(config.find("threaded") != config.end())
88 shared->threaded = config["threaded"] == "true";
91 if(config.find("kinect") != config.end())
93 shared->kinect = config["kinect"] == "true";
96 if(config.find("fps") != config.end())
98 shared->fps = boost::lexical_cast<int>(config["fps"]);
101 if(config.find("device") != config.end())
103 device = config["device"].c_str();
106 if(config.find("pixelLowerBound") != config.end())
108 shared->pixelLowerBound = boost::lexical_cast<int>(config["pixelLowerBound"]);
110 if(shared->pixelLowerBound < 0)
112 shared->pixelLowerBound = 0;
116 if(config.find("pixelUpperBound") != config.end())
118 shared->pixelUpperBound = boost::lexical_cast<int>(config["pixelUpperBound"]);
120 if(shared->pixelUpperBound > 255)
121 shared->pixelUpperBound = 255;
124 if(config.find("opencl") != config.end())
126 if(cv::ocl::haveOpenCL())
128 bool useOcl = config["opencl"] == "true";
129 cv::ocl::setUseOpenCL(useOcl);
130 openCl->setValue(cv::ocl::useOpenCL());
134 DebugOut(DebugOut::Warning) << "You really don't have openCL support." << endl;
138 if(config.find("ddd") != config.end())
140 shared->ddd = config["ddd"] == "true";
143 if(config.find("logging") != config.end())
145 shared->loggingOn = config["logging"] == "true";
150 faceCascade = amb::make_unique(new cv::CascadeClassifier());
151 eyeCascade = amb::make_unique(new cv::CascadeClassifier());
153 std::string faceFile = config["faceCascade"];
155 if(!faceCascade->load(faceFile))
157 DebugOut(DebugOut::Warning) << "Could not load face cascade: " << faceFile <<". Disabling ddd" << endl;
161 std::string eyeFile = config["eyeCascade"];
163 if(!eyeCascade->load(eyeFile))
165 DebugOut(DebugOut::Warning) << "Could not load eye cascade: " << eyeFile <<". Disabling ddd" << endl;
171 routingEngine->subscribeToProperty(VehicleProperty::VehicleSpeed, &parent);
172 routingEngine->subscribeToProperty(VehicleProperty::Latitude, &parent);
173 routingEngine->subscribeToProperty(VehicleProperty::Longitude, &parent);
176 OpenCvLuxPlugin::~OpenCvLuxPlugin()
181 const string OpenCvLuxPlugin::uuid() const
183 return "3c7a1ea0-7d2e-11e2-9e96-0800200c9a66";
186 void OpenCvLuxPlugin::getPropertyAsync(AsyncPropertyReply *reply)
188 reply->timedout = [this](AsyncPropertyReply* reply) {
189 removeOne(&replyQueue, reply);
192 if(!shared->m_capture || !shared->m_capture->isOpened())
194 /// we want to turn on the camera for one shot to get an image and determine the intensity
197 grabImage(shared.get());
200 AmbPluginImpl::getPropertyAsync(reply);
203 void OpenCvLuxPlugin::getRangePropertyAsync(AsyncRangePropertyReply *reply)
205 throw std::runtime_error("OpenCVLuxPlugin does not support this operation. We should never hit this method.");
208 AsyncPropertyReply *OpenCvLuxPlugin::setProperty(AsyncSetPropertyRequest request )
210 if(request.property == VideoLogging)
212 shared->loggingOn = request.value->value<bool>();
214 if(request.property == OpenCL)
216 QMutexLocker lock(&mutex);
217 cv::ocl::setUseOpenCL(request.value->value<bool>());
220 return AmbPluginImpl::setProperty(request);;
223 void OpenCvLuxPlugin::subscribeToPropertyChanges(VehicleProperty::Property property)
225 if(!shared->mRequests.size())
228 g_timeout_add(1000 / shared->fps, grabImage, shared.get());
231 shared->mRequests.push_back(property);
234 void OpenCvLuxPlugin::unsubscribeToPropertyChanges(VehicleProperty::Property property)
236 removeOne(&shared->mRequests,property);
239 void OpenCvLuxPlugin::propertyChanged(AbstractPropertyType *value)
241 QMutexLocker lock(&mutex);
242 if(value->name == VehicleProperty::VehicleSpeed)
244 speed = value->value<uint16_t>();
246 else if(value->name == VehicleProperty::Latitude)
248 latitude = value->value<double>();
250 else if(value->name == VehicleProperty::Longitude)
252 longitude = value->value<double>();
256 void OpenCvLuxPlugin::supportedChanged(const PropertyList &)
258 DebugOut()<<"OpenCvLuxPlugin::supported changed."<<endl;
261 static int grabImage(void *data)
263 OpenCvLuxPlugin::Shared* shared = static_cast<OpenCvLuxPlugin::Shared*>(data);
265 shared->frameCount++;
267 if(shared->frameCount >= shared->fps)
268 shared->frameCount = 0;
270 if(shared->frameCount % 4 != 0)
275 if(!shared->m_capture->isOpened())
280 *(shared->m_capture.get()) >> m_image;
286 shared->parent->detectEyes(m_image);
290 DebugOut(DebugOut::Warning) << "DDD detection exception: "<< endl;
296 QFutureWatcher<uint> *watcher = new QFutureWatcher<uint>();
297 QObject::connect(watcher, &QFutureWatcher<uint>::finished, shared->parent, &OpenCvLuxPlugin::imgProcResult);
299 QFuture<uint> future = QtConcurrent::run(evalImage, m_image, shared);
300 watcher->setFuture(future);
302 QtConcurrent::run(shared->parent, &OpenCvLuxPlugin::writeVideoFrame, m_image);
306 if(shared->parent->videoLogging->value<bool>())
307 shared->parent->writeVideoFrame(m_image);
310 uint16_t lux = evalImage(m_image, shared);
311 shared->parent->updateProperty(lux);
315 DebugOut(DebugOut::Warning) << "Exception caught "<< __FUNCTION__ << endl;
318 //detectLight(m_image,shared);
321 if(shared->mRequests.size())
329 static uint evalImage(cv::UMat qImg, OpenCvLuxPlugin::Shared *shared)
333 DebugOut(DebugOut::Warning)<<"Empty image frame."<<endl;
337 cv::Scalar avgPixelIntensity;
339 avgPixelIntensity = cv::mean(qImg);
341 double val = avgPixelIntensity.val[0];
343 double qualifiedPixel = (val - shared->pixelLowerBound);
345 if(qualifiedPixel < 0) qualifiedPixel = 0;
347 uint lux = qualifiedPixel * (130000.0 / shared->pixelUpperBound);
349 DebugOut(7)<<"average pixel value: "<<qualifiedPixel<<" lux: "<<lux<<endl;
355 void OpenCvLuxPlugin::init()
357 if(!shared->m_capture)
360 shared->m_capture = amb::make_unique(new cv::VideoCapture(0));
362 shared->m_capture = amb::make_unique(new cv::VideoCapture(atoi(device.c_str())));
365 if(!shared->m_capture->isOpened())
367 DebugOut() << "we failed to open camera device (" << device << ") or no camera found" << endl;
371 if(!shared->mWriter || !shared->mWriter->isOpened())
373 cv::Size s = cv::Size((int) shared->m_capture->get(cv::CAP_PROP_FRAME_WIDTH),
374 (int) shared->m_capture->get(cv::CAP_PROP_FRAME_HEIGHT));
376 std::string codec = configuration["codec"];
378 if(codec.empty() || codec.size() != 4)
380 DebugOut(DebugOut::Warning)<<"Invalid codec: "<<codec <<". Using default: MJPG"<<endl;
384 std::string filename = configuration["logfile"];
386 if(filename.empty()) filename = "/tmp/video.avi";
388 boost::algorithm::to_upper(codec);
390 shared->mWriter = amb::make_unique(new cv::VideoWriter(filename, cv::VideoWriter::fourcc(codec.at(0), codec.at(1), codec.at(2), codec.at(3)),30,s));
393 DebugOut()<<"camera frame width: "<<shared->m_capture->get(cv::CAP_PROP_FRAME_WIDTH)<<endl;
394 DebugOut()<<"camera frame height: "<<shared->m_capture->get(cv::CAP_PROP_FRAME_HEIGHT)<<endl;
395 DebugOut()<<"camera frame fps: "<<shared->m_capture->get(cv::CAP_PROP_FPS)<<endl;
402 void OpenCvLuxPlugin::writeVideoFrame(cv::UMat f)
404 QMutexLocker locker(&mutex);
406 if(shared->loggingOn && speed > 0)
411 std::stringstream str;
412 str<<"Speed: "<<speed<<" kph, Lat/Lon: "<<latitude<<"/"<<longitude;
414 std::stringstream datestr;
415 datestr<<QDateTime::currentDateTime().toString().toStdString();
419 std::string text = str.str();
421 int fontFace = cv::FONT_HERSHEY_SIMPLEX;
422 double fontScale = 1;
426 cv::Size textSize = cv::getTextSize(text, fontFace,
427 fontScale, thickness, &baseline);
429 cv::Size dateTextSize = cv::getTextSize(datestr.str(), fontFace,
430 fontScale, thickness, &baseline);
432 baseline += thickness;
435 cv::Point textOrg((frame.cols - textSize.width)/2,
436 (frame.rows - textSize.height));
438 cv::Point dateOrg((frame.cols - dateTextSize.width)/2, dateTextSize.height);
440 // then put the text itself
441 cv::putText(frame, text, textOrg, fontFace, fontScale,
442 cv::Scalar::all(255), thickness, 8);
444 cv::putText(frame, datestr.str(), dateOrg, fontFace, fontScale,
445 cv::Scalar::all(255), thickness, 8);
447 (*shared->mWriter) << frame;
448 (*shared->mWriter) << frame;
452 void OpenCvLuxPlugin::updateProperty(uint16_t lux)
454 extBrightness->setValue(lux);
456 if(lux != lastLux && shared->mRequests.size())
459 routingEngine->updateProperty(extBrightness.get(), uuid());
463 void OpenCvLuxPlugin::imgProcResult()
467 QFutureWatcher<uint> *watcher = dynamic_cast<QFutureWatcher<uint>*>(sender());
469 uint lux = watcher->result();
470 shared->parent->updateProperty(lux);
472 watcher->deleteLater();
476 DebugOut(DebugOut::Warning) << "exception caught getting img processing result" << endl;
480 TrafficLight::Color detectLight(cv::UMat img, OpenCvLuxPlugin::Shared *shared)
485 cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
486 cv::GaussianBlur(gray, gray, cv::Size(9,9), 2, 2);
488 std::vector<cv::Vec3f> circles;
490 //cv::HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, gray.rows/8, 200, 100, 0, 0);
492 cv::HoughCircles( gray, circles, cv::HOUGH_GRADIENT, 2, 30, 231, 50, 0, 0 );
494 for(auto i = 0; i < circles.size(); i++)
496 cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
497 int radius = cvRound(circles[i][2]);
499 cv::Rect rect(center.x - radius / 2, center.y - radius / 2, radius, radius);
503 cv::UMat light(img, rect);
505 cv::circle(img, center, radius, cv::Scalar(0,0,255), 3, 8, 0 );
507 cv::rectangle(img, rect.tl(), rect.br(), cv::Scalar(255,0,0));
509 cv::Scalar avgPixel = cv::mean(light);
511 if(avgPixel[2] > 128 && avgPixel[0] < 128 && avgPixel[1] < 128)
513 DebugOut(1)<<"Red Light!!!"<<endl;
514 return TrafficLight::Red;
516 else if(avgPixel[1] > 128 && avgPixel[0] < 128 && avgPixel[2] < 128)
518 DebugOut(1)<<"Green Light!!!"<<endl;
519 return TrafficLight::Green;
521 else if(avgPixel[0] > 128 && avgPixel[1] < 128 && avgPixel[2] < 128)
523 DebugOut(1)<<"Yellow Light!!!"<<endl;
524 return TrafficLight::Yellow;
536 void OpenCvLuxPlugin::detectEyes(cv::UMat frame)
541 bool hasEyes = false;
542 std::vector<cv::Rect> faces;
546 cv::cvtColor(frame, frameGray, cv::COLOR_BGR2GRAY);
547 cv::equalizeHist(frameGray, frameGray);
548 faceCascade->detectMultiScale(frameGray, faces, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
550 for( size_t i = 0; i < faces.size(); i++ )
552 cv::Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
553 cv::ellipse( frame, center, cv::Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, cv::Scalar( 255, 0, 255 ), 4, 8, 0 );
555 std::vector<cv::Rect> eyes;
558 cv::UMat faceROI(frameGray(faces[i]));
559 eyeCascade->detectMultiScale( faceROI, eyes, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30) );
561 for( size_t j = 0; j < eyes.size(); j++ )
563 cv::Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5, faces[i].y + eyes[j].y + eyes[j].height*0.5 );
564 int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
565 cv::circle( frame, center, radius, cv::Scalar( 255, 0, 0 ), 4, 8, 0 );
571 DebugOut() << "Number of eyes: " << eyes.size() << endl;
573 if(driverDrowsiness->value<bool>())
575 driverDrowsiness->setValue(false);
576 routingEngine->updateProperty(driverDrowsiness.get(), this->uuid());
581 if(!driverDrowsiness->value<bool>())
583 driverDrowsiness->setValue(true);
584 routingEngine->updateProperty(driverDrowsiness.get(), this->uuid());