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>
36 #include <opencv2/core/ocl.hpp>
41 const std::string VideoLogging = "VideoLogging";
42 const std::string DriverDrowsiness = "DriverDrowsiness";
43 const std::string OpenCL = "OpenCL";
47 extern "C" AbstractSource * create(AbstractRoutingEngine* routingengine, map<string, string> config)
49 auto plugin = new AmbPlugin<OpenCvLuxPlugin>(routingengine, config);
55 OpenCvLuxPlugin::OpenCvLuxPlugin(AbstractRoutingEngine* re, map<string, string> config, AbstractSource &parent)
56 :AmbPluginImpl(re, config, parent), lastLux(0), speed(0), latitude(0), longitude(0)
58 videoLogging = addPropertySupport(Zone::None, [](){
59 return new BasicPropertyType<bool>(VideoLogging, false);
62 driverDrowsiness = addPropertySupport(Zone::None, [](){
63 return new OpenCvLuxPlugin::DriverDrowsinessType(DriverDrowsiness, false);
66 openCl = addPropertySupport(Zone::None, [](){
67 return new BasicPropertyType<bool>(OpenCL, false);
70 extBrightness = addPropertySupport<VehicleProperty::ExteriorBrightnessType>(Zone::None);
72 shared = amb::make_unique(new Shared);
73 shared->parent = this;
75 shared->m_capture = NULL;
76 shared->mWriter = nullptr;
78 shared->threaded = false;
79 shared->kinect = false;
80 shared->useCuda = false;
81 shared->loggingOn = false;
86 shared->pixelLowerBound = 0;
87 shared->pixelUpperBound = 255;
90 if(config.find("threaded") != config.end())
92 shared->threaded = config["threaded"] == "true";
95 if(config.find("kinect") != config.end())
97 shared->kinect = config["kinect"] == "true";
100 if(config.find("fps") != config.end())
102 shared->fps = boost::lexical_cast<int>(config["fps"]);
105 if(config.find("device") != config.end())
107 device = config["device"].c_str();
110 if(config.find("pixelLowerBound") != config.end())
112 shared->pixelLowerBound = boost::lexical_cast<int>(config["pixelLowerBound"]);
114 if(shared->pixelLowerBound < 0)
116 shared->pixelLowerBound = 0;
120 if(config.find("pixelUpperBound") != config.end())
122 shared->pixelUpperBound = boost::lexical_cast<int>(config["pixelUpperBound"]);
124 if(shared->pixelUpperBound > 255)
125 shared->pixelUpperBound = 255;
128 if(config.find("opencl") != config.end())
130 if(cv::ocl::haveOpenCL())
132 bool useOcl = config["opencl"] == "true";
133 cv::ocl::setUseOpenCL(useOcl);
134 openCl->setValue(cv::ocl::useOpenCL());
138 DebugOut(DebugOut::Warning) << "You really don't have openCL support." << endl;
142 if(config.find("ddd") != config.end())
144 shared->ddd = config["ddd"] == "true";
147 if(config.find("logging") != config.end())
149 shared->loggingOn = config["logging"] == "true";
154 faceCascade = amb::make_unique(new cv::CascadeClassifier());
155 eyeCascade = amb::make_unique(new cv::CascadeClassifier());
157 std::string faceFile = config["faceCascade"];
159 if(!faceCascade->load(faceFile))
161 DebugOut(DebugOut::Warning) << "Could not load face cascade: " << faceFile <<". Disabling ddd" << endl;
165 std::string eyeFile = config["eyeCascade"];
167 if(!eyeCascade->load(eyeFile))
169 DebugOut(DebugOut::Warning) << "Could not load eye cascade: " << eyeFile <<". Disabling ddd" << endl;
175 routingEngine->subscribeToProperty(VehicleProperty::VehicleSpeed, &parent);
176 routingEngine->subscribeToProperty(VehicleProperty::Latitude, &parent);
177 routingEngine->subscribeToProperty(VehicleProperty::Longitude, &parent);
180 OpenCvLuxPlugin::~OpenCvLuxPlugin()
185 const string OpenCvLuxPlugin::uuid() const
187 return "3c7a1ea0-7d2e-11e2-9e96-0800200c9a66";
190 void OpenCvLuxPlugin::getPropertyAsync(AsyncPropertyReply *reply)
192 reply->timedout = [this](AsyncPropertyReply* reply) {
193 removeOne(&replyQueue, reply);
196 if(!shared->m_capture || !shared->m_capture->isOpened())
198 /// we want to turn on the camera for one shot to get an image and determine the intensity
201 grabImage(shared.get());
204 AmbPluginImpl::getPropertyAsync(reply);
207 void OpenCvLuxPlugin::getRangePropertyAsync(AsyncRangePropertyReply *reply)
209 throw std::runtime_error("OpenCVLuxPlugin does not support this operation. We should never hit this method.");
212 AsyncPropertyReply *OpenCvLuxPlugin::setProperty(AsyncSetPropertyRequest request )
214 if(request.property == VideoLogging)
216 shared->loggingOn = request.value->value<bool>();
218 if(request.property == OpenCL)
220 QMutexLocker lock(&mutex);
221 cv::ocl::setUseOpenCL(request.value->value<bool>());
224 return AmbPluginImpl::setProperty(request);;
227 void OpenCvLuxPlugin::subscribeToPropertyChanges(VehicleProperty::Property property)
229 if(!shared->mRequests.size())
232 g_timeout_add(1000 / shared->fps, grabImage, shared.get());
235 shared->mRequests.push_back(property);
238 void OpenCvLuxPlugin::unsubscribeToPropertyChanges(VehicleProperty::Property property)
240 removeOne(&shared->mRequests,property);
243 void OpenCvLuxPlugin::propertyChanged(AbstractPropertyType *value)
245 QMutexLocker lock(&mutex);
246 if(value->name == VehicleProperty::VehicleSpeed)
248 speed = value->value<uint16_t>();
250 else if(value->name == VehicleProperty::Latitude)
252 latitude = value->value<double>();
254 else if(value->name == VehicleProperty::Longitude)
256 longitude = value->value<double>();
260 void OpenCvLuxPlugin::supportedChanged(const PropertyList &)
262 DebugOut()<<"OpenCvLuxPlugin::supported changed."<<endl;
265 static int grabImage(void *data)
267 OpenCvLuxPlugin::Shared* shared = static_cast<OpenCvLuxPlugin::Shared*>(data);
269 shared->frameCount++;
271 if(shared->frameCount >= shared->fps)
272 shared->frameCount = 0;
274 if(shared->frameCount % 4 != 0)
279 if(!shared->m_capture->isOpened())
284 *(shared->m_capture.get()) >> m_image;
290 shared->parent->detectEyes(m_image);
294 DebugOut(DebugOut::Warning) << "DDD detection exception: "<< endl;
300 QFutureWatcher<uint> *watcher = new QFutureWatcher<uint>();
301 QObject::connect(watcher, &QFutureWatcher<uint>::finished, shared->parent, &OpenCvLuxPlugin::imgProcResult);
303 QFuture<uint> future = QtConcurrent::run(evalImage, m_image, shared);
304 watcher->setFuture(future);
306 QtConcurrent::run(shared->parent, &OpenCvLuxPlugin::writeVideoFrame, m_image);
310 if(shared->parent->videoLogging->value<bool>())
311 shared->parent->writeVideoFrame(m_image);
314 uint16_t lux = evalImage(m_image, shared);
315 shared->parent->updateProperty(lux);
319 DebugOut(DebugOut::Warning) << "Exception caught "<< __FUNCTION__ << endl;
322 //detectLight(m_image,shared);
325 if(shared->mRequests.size())
333 static uint evalImage(cv::UMat qImg, OpenCvLuxPlugin::Shared *shared)
337 DebugOut(DebugOut::Warning)<<"Empty image frame."<<endl;
341 cv::Scalar avgPixelIntensity;
343 avgPixelIntensity = cv::mean(qImg);
345 double val = avgPixelIntensity.val[0];
347 double qualifiedPixel = (val - shared->pixelLowerBound);
349 if(qualifiedPixel < 0) qualifiedPixel = 0;
351 uint lux = qualifiedPixel * (130000.0 / shared->pixelUpperBound);
353 DebugOut(7)<<"average pixel value: "<<qualifiedPixel<<" lux: "<<lux<<endl;
359 void OpenCvLuxPlugin::init()
361 if(!shared->m_capture)
364 shared->m_capture = amb::make_unique(new cv::VideoCapture(0));
366 shared->m_capture = amb::make_unique(new cv::VideoCapture(atoi(device.c_str())));
369 if(!shared->m_capture->isOpened())
371 DebugOut() << "we failed to open camera device (" << device << ") or no camera found" << endl;
375 if(!shared->mWriter || !shared->mWriter->isOpened())
377 cv::Size s = cv::Size((int) shared->m_capture->get(cv::CAP_PROP_FRAME_WIDTH),
378 (int) shared->m_capture->get(cv::CAP_PROP_FRAME_HEIGHT));
380 std::string codec = configuration["codec"];
382 if(codec.empty() || codec.size() != 4)
384 DebugOut(DebugOut::Warning)<<"Invalid codec: "<<codec <<". Using default: MJPG"<<endl;
388 std::string filename = configuration["logfile"];
390 if(filename.empty()) filename = "/tmp/video.avi";
392 boost::algorithm::to_upper(codec);
394 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));
397 DebugOut()<<"camera frame width: "<<shared->m_capture->get(cv::CAP_PROP_FRAME_WIDTH)<<endl;
398 DebugOut()<<"camera frame height: "<<shared->m_capture->get(cv::CAP_PROP_FRAME_HEIGHT)<<endl;
399 DebugOut()<<"camera frame fps: "<<shared->m_capture->get(cv::CAP_PROP_FPS)<<endl;
406 void OpenCvLuxPlugin::writeVideoFrame(cv::UMat f)
408 QMutexLocker locker(&mutex);
410 if(shared->loggingOn && speed > 0)
415 std::stringstream str;
416 str<<"Speed: "<<speed<<" kph, Lat/Lon: "<<latitude<<"/"<<longitude;
418 std::stringstream datestr;
419 datestr<<QDateTime::currentDateTime().toString().toStdString();
423 std::string text = str.str();
425 int fontFace = cv::FONT_HERSHEY_SIMPLEX;
426 double fontScale = 1;
430 cv::Size textSize = cv::getTextSize(text, fontFace,
431 fontScale, thickness, &baseline);
433 cv::Size dateTextSize = cv::getTextSize(datestr.str(), fontFace,
434 fontScale, thickness, &baseline);
436 baseline += thickness;
439 cv::Point textOrg((frame.cols - textSize.width)/2,
440 (frame.rows - textSize.height));
442 cv::Point dateOrg((frame.cols - dateTextSize.width)/2, dateTextSize.height);
444 // then put the text itself
445 cv::putText(frame, text, textOrg, fontFace, fontScale,
446 cv::Scalar::all(255), thickness, 8);
448 cv::putText(frame, datestr.str(), dateOrg, fontFace, fontScale,
449 cv::Scalar::all(255), thickness, 8);
451 (*shared->mWriter) << frame;
452 (*shared->mWriter) << frame;
456 void OpenCvLuxPlugin::updateProperty(uint16_t lux)
458 extBrightness->setValue(lux);
460 if(lux != lastLux && shared->mRequests.size())
463 routingEngine->updateProperty(extBrightness.get(), uuid());
467 void OpenCvLuxPlugin::imgProcResult()
471 QFutureWatcher<uint> *watcher = dynamic_cast<QFutureWatcher<uint>*>(sender());
473 uint lux = watcher->result();
474 shared->parent->updateProperty(lux);
476 watcher->deleteLater();
480 DebugOut(DebugOut::Warning) << "exception caught getting img processing result" << endl;
484 TrafficLight::Color detectLight(cv::UMat img, OpenCvLuxPlugin::Shared *shared)
489 cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
490 cv::GaussianBlur(gray, gray, cv::Size(9,9), 2, 2);
492 std::vector<cv::Vec3f> circles;
494 //cv::HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, gray.rows/8, 200, 100, 0, 0);
496 cv::HoughCircles( gray, circles, cv::HOUGH_GRADIENT, 2, 30, 231, 50, 0, 0 );
498 for(auto i = 0; i < circles.size(); i++)
500 cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
501 int radius = cvRound(circles[i][2]);
503 cv::Rect rect(center.x - radius / 2, center.y - radius / 2, radius, radius);
507 cv::UMat light(img, rect);
509 cv::circle(img, center, radius, cv::Scalar(0,0,255), 3, 8, 0 );
511 cv::rectangle(img, rect.tl(), rect.br(), cv::Scalar(255,0,0));
513 cv::Scalar avgPixel = cv::mean(light);
515 if(avgPixel[2] > 128 && avgPixel[0] < 128 && avgPixel[1] < 128)
517 DebugOut(1)<<"Red Light!!!"<<endl;
518 return TrafficLight::Red;
520 else if(avgPixel[1] > 128 && avgPixel[0] < 128 && avgPixel[2] < 128)
522 DebugOut(1)<<"Green Light!!!"<<endl;
523 return TrafficLight::Green;
525 else if(avgPixel[0] > 128 && avgPixel[1] < 128 && avgPixel[2] < 128)
527 DebugOut(1)<<"Yellow Light!!!"<<endl;
528 return TrafficLight::Yellow;
540 void OpenCvLuxPlugin::detectEyes(cv::UMat frame)
545 bool hasEyes = false;
546 std::vector<cv::Rect> faces;
550 cv::cvtColor(frame, frameGray, cv::COLOR_BGR2GRAY);
551 cv::equalizeHist(frameGray, frameGray);
552 faceCascade->detectMultiScale(frameGray, faces, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
554 for( size_t i = 0; i < faces.size(); i++ )
556 cv::Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
557 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 );
559 std::vector<cv::Rect> eyes;
562 cv::UMat faceROI(frameGray(faces[i]));
563 eyeCascade->detectMultiScale( faceROI, eyes, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30) );
565 for( size_t j = 0; j < eyes.size(); j++ )
567 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 );
568 int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
569 cv::circle( frame, center, radius, cv::Scalar( 255, 0, 0 ), 4, 8, 0 );
575 DebugOut() << "Number of eyes: " << eyes.size() << endl;
577 if(driverDrowsiness->value<bool>())
579 driverDrowsiness->setValue(false);
580 routingEngine->updateProperty(driverDrowsiness.get(), this->uuid());
585 if(!driverDrowsiness->value<bool>())
587 driverDrowsiness->setValue(true);
588 routingEngine->updateProperty(driverDrowsiness.get(), this->uuid());