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>
25 #include <opencv2/imgproc/imgproc.hpp>
26 #include <opencv2/objdetect/objdetect.hpp>
29 #include <QFutureWatcher>
30 #include <QtConcurrent/QtConcurrent>
34 #include <opencv2/ocl/ocl.hpp>
38 #include <opencv2/gpu/gpu.hpp>
43 const std::string VideoLogging = "VideoLogging";
44 const std::string DriverDrowsiness = "DriverDrowsiness";
45 const std::string OpenCL = "OpenCL";
49 extern "C" AbstractSource * create(AbstractRoutingEngine* routingengine, map<string, string> config)
51 VehicleProperty::registerProperty(VideoLogging, [](){
52 return new BasicPropertyType<bool>(VideoLogging, false);
55 VehicleProperty::registerProperty(DriverDrowsiness, [](){
56 return new OpenCvLuxPlugin::DriverDrowsinessType(DriverDrowsiness, false);
59 VehicleProperty::registerProperty(OpenCL, [](){
60 return new BasicPropertyType<bool>(OpenCL, false);
63 return new OpenCvLuxPlugin(routingengine, config);
66 OpenCvLuxPlugin::OpenCvLuxPlugin(AbstractRoutingEngine* re, map<string, string> config)
67 :AbstractSource(re, config), lastLux(0), speed(0), latitude(0), longitude(0), extBrightness(new VehicleProperty::ExteriorBrightnessType(0))
69 driverDrowsiness = amb::make_unique(new DriverDrowsinessType(DriverDrowsiness, false));
70 openCl = amb::make_unique(new BasicPropertyType<bool>(OpenCL, false));
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->useOpenCl = false;
81 shared->useCuda = false;
82 shared->loggingOn = false;
87 shared->pixelLowerBound = 0;
88 shared->pixelUpperBound = 255;
91 if(config.find("threaded") != config.end())
93 shared->threaded = config["threaded"] == "true";
96 if(config.find("kinect") != config.end())
98 shared->kinect = config["kinect"] == "true";
101 if(config.find("fps") != config.end())
103 shared->fps = boost::lexical_cast<int>(config["fps"]);
106 if(config.find("device") != config.end())
108 device = config["device"].c_str();
111 if(config.find("pixelLowerBound") != config.end())
113 shared->pixelLowerBound = boost::lexical_cast<int>(config["pixelLowerBound"]);
115 if(shared->pixelLowerBound < 0)
117 shared->pixelLowerBound = 0;
121 if(config.find("pixelUpperBound") != config.end())
123 shared->pixelUpperBound = boost::lexical_cast<int>(config["pixelUpperBound"]);
125 if(shared->pixelUpperBound > 255)
126 shared->pixelUpperBound = 255;
129 if(config.find("opencl") != config.end())
132 shared->useOpenCl = config["opencl"] == "true";
133 openCl->setValue(shared->useOpenCl);
135 DebugOut(DebugOut::Warning) << "You really don't have openCL support. Disabling." << endl;
136 shared->useOpenCl = false;
141 if(config.find("cuda") != config.end())
144 shared->useCuda = config["cuda"] == "true";
148 if(config.find("ddd") != config.end())
150 shared->ddd = config["ddd"] == "true";
153 if(config.find("logging") != config.end())
155 shared->loggingOn = config["logging"] == "true";
161 cv::ocl::PlatformsInfo platforms;
162 cv::ocl::getOpenCLPlatforms(platforms);
164 for(auto p : platforms)
166 DebugOut(1)<<"platform: "<<p->platformName<<" vendor: "<<p->platformVendor<<endl;
169 cv::ocl::DevicesInfo info;
170 cv::ocl::getOpenCLDevices(info, cv::ocl::CVCL_DEVICE_TYPE_ALL);
172 DebugOut(1)<<"There are "<<info.size()<<" OpenCL devices on this system"<<endl;
176 DebugOut(1)<<"No CL devices. Disabling OpenCL"<<endl;
177 shared->useOpenCl = false;
181 cv::ocl::setDevice(info[0]);
186 for(auto name : i->deviceName)
188 DebugOut(1)<<"OpenCLDeviceName: "<<name<<endl;
197 int devices = cv::gpu::getCudaEnabledDeviceCount();
198 DebugOut()<<"There are "<<devices<<" CUDA devices on this system"<<endl;
201 DebugOut()<<"We will use 0 as the default device"<<endl;
202 cv::gpu::DeviceInfo info(0);
203 DebugOut()<<"Cuda Device Name: "<<info.name()<<endl;
204 DebugOut()<<"Version: "<<info.majorVersion()<<"major"<<info.minorVersion()<<"minor"<<endl;
205 DebugOut()<<"Streaming processor count: "<<info.multiProcessorCount()<<endl;
206 cv::gpu::setDevice(0);
210 DebugOut(DebugOut::Warning)<<"No CUDA device found. Disabling CUDA."<<endl;
211 shared->useCuda = false;
219 faceCascade = amb::make_unique(new cv::ocl::OclCascadeClassifier());
220 eyeCascade = amb::make_unique(new cv::ocl::OclCascadeClassifier());
222 faceCascade = amb::make_unique(new cv::CascadeClassifier());
223 eyeCascade = amb::make_unique(new cv::CascadeClassifier());
226 std::string faceFile = CV_DATA "/haarcascades/haarcascade_frontalface_alt.xml";
227 if(!faceCascade->load(faceFile))
229 DebugOut(DebugOut::Warning) << "Could not load face cascade: " << faceFile <<". Disabling ddd" << endl;
233 std::string eyeFile = CV_DATA "/haarcascades/haarcascade_eye_tree_eyeglasses.xml";
235 if(!eyeCascade->load(eyeFile))
237 DebugOut(DebugOut::Warning) << "Could not load eye cascade: " << eyeFile <<". Disabling ddd" << endl;
243 routingEngine->subscribeToProperty(VehicleProperty::VehicleSpeed, this);
244 routingEngine->subscribeToProperty(VehicleProperty::Latitude, this);
245 routingEngine->subscribeToProperty(VehicleProperty::Longitude, this);
248 OpenCvLuxPlugin::~OpenCvLuxPlugin()
253 const string OpenCvLuxPlugin::uuid()
255 return "3c7a1ea0-7d2e-11e2-9e96-0800200c9a66";
258 void OpenCvLuxPlugin::getPropertyAsync(AsyncPropertyReply *reply)
260 reply->timedout = [this](AsyncPropertyReply* reply) {
261 removeOne(&replyQueue, reply);
264 if(!shared->m_capture || !shared->m_capture->isOpened())
266 /// we want to turn on the camera for one shot to get an image and determine the intensity
269 grabImage(shared.get());
272 if(reply->property == VehicleProperty::ExteriorBrightness)
274 replyQueue.push_back(reply);
276 else if(reply->property == VideoLogging)
278 BasicPropertyType<bool> tmp(VideoLogging, shared->loggingOn);
280 reply->success = true;
281 reply->completed(reply);
283 else ///We don't support what you are asking for. Reply false
285 reply->value = nullptr;
286 reply->success = false;
287 reply->error = AsyncPropertyReply::InvalidOperation;
288 reply->completed(reply);
292 void OpenCvLuxPlugin::getRangePropertyAsync(AsyncRangePropertyReply *reply)
294 throw std::runtime_error("OpenCVLuxPlugin does not support this operation. We should never hit this method.");
297 AsyncPropertyReply *OpenCvLuxPlugin::setProperty(AsyncSetPropertyRequest request )
299 AsyncPropertyReply *reply = new AsyncPropertyReply(request);
301 reply->success = false;
302 reply->error = AsyncPropertyReply::InvalidOperation;
304 if(request.property == VideoLogging)
306 shared->loggingOn = request.value->value<bool>();
307 reply->success = true;
308 reply->error = AsyncPropertyReply::NoError;
311 reply->completed(reply);
316 void OpenCvLuxPlugin::subscribeToPropertyChanges(VehicleProperty::Property property)
318 if(!shared->mRequests.size())
321 g_timeout_add(1000 / shared->fps, grabImage, shared.get());
324 shared->mRequests.push_back(property);
327 void OpenCvLuxPlugin::unsubscribeToPropertyChanges(VehicleProperty::Property property)
329 removeOne(&shared->mRequests,property);
332 PropertyList OpenCvLuxPlugin::supported()
335 props.push_back(VehicleProperty::ExteriorBrightness);
336 props.push_back(DriverDrowsiness);
337 props.push_back(VideoLogging);
342 int OpenCvLuxPlugin::supportedOperations()
347 void OpenCvLuxPlugin::propertyChanged(AbstractPropertyType *value)
349 QMutexLocker lock(&mutex);
350 if(value->name == VehicleProperty::VehicleSpeed)
352 speed = value->value<uint16_t>();
354 else if(value->name == VehicleProperty::Latitude)
356 latitude = value->value<double>();
358 else if(value->name == VehicleProperty::Longitude)
360 longitude = value->value<double>();
364 void OpenCvLuxPlugin::supportedChanged(const PropertyList &)
366 DebugOut()<<"OpenCvLuxPlugin::supported changed."<<endl;
369 static int grabImage(void *data)
371 OpenCvLuxPlugin::Shared* shared = static_cast<OpenCvLuxPlugin::Shared*>(data);
377 shared->m_capture->grab();
378 shared->m_capture->retrieve( m_image, CV_CAP_OPENNI_GRAY_IMAGE );
382 *(shared->m_capture.get()) >> m_image;
389 shared->parent->detectEyes(m_image);
393 DebugOut(DebugOut::Warning) << "DDD detection exception: "<< endl;
399 QFutureWatcher<uint> *watcher = new QFutureWatcher<uint>();
400 QObject::connect(watcher, &QFutureWatcher<uint>::finished, shared->parent, &OpenCvLuxPlugin::imgProcResult);
402 QFuture<uint> future = QtConcurrent::run(evalImage, m_image, shared);
403 watcher->setFuture(future);
405 QtConcurrent::run(shared->parent, &OpenCvLuxPlugin::writeVideoFrame, m_image);
409 shared->parent->writeVideoFrame(m_image);
412 int lux = evalImage(m_image, shared);
413 shared->parent->updateProperty(lux);
417 DebugOut(DebugOut::Warning) << "Exception caught "<< __FUNCTION__ << endl;
420 //detectLight(m_image,shared);
424 if(shared->mRequests.size())
432 static uint evalImage(cv::Mat qImg, OpenCvLuxPlugin::Shared *shared)
436 DebugOut(DebugOut::Warning)<<"Empty image frame."<<endl;
440 cv::Scalar avgPixelIntensity;
443 if(shared->useOpenCl)
447 cv::ocl::oclMat src(qImg), gray;
448 cv::ocl::cvtColor(src, gray, CV_BGR2GRAY);
449 cv::ocl::meanStdDev(src, avgPixelIntensity, stdDev);
452 else if(shared->useCuda)
455 cv::gpu::GpuMat src(qImg), dest;
456 cv::gpu::cvtColor(src, dest, CV_BGR2GRAY);
461 cv::gpu::meanStdDev(dest, avgPixelIntensity, stdDev);
465 DebugOut(DebugOut::Error)<<"CUDA pixel intensity calculation failed."<<endl;
471 avgPixelIntensity = cv::mean(qImg);
475 double val = avgPixelIntensity.val[0];
477 double qualifiedPixel = (val - shared->pixelLowerBound);
479 if(qualifiedPixel < 0) qualifiedPixel = 0;
481 uint lux = qualifiedPixel * (130000.0 / shared->pixelUpperBound);
483 DebugOut(7)<<"average pixel value: "<<qualifiedPixel<<" lux: "<<lux<<endl;
489 bool OpenCvLuxPlugin::init()
491 if(!shared->m_capture && shared->kinect)
493 shared->m_capture = amb::make_unique(new cv::VideoCapture(CV_CAP_OPENNI));
495 else if(!shared->m_capture)
498 shared->m_capture = amb::make_unique(new cv::VideoCapture(0));
500 shared->m_capture = amb::make_unique(new cv::VideoCapture(atoi(device.c_str())));
503 if(!shared->m_capture->isOpened())
505 DebugOut()<<"we failed to open camera device ("<<device<<") or no camera found"<<endl;
509 if(configuration.find("logging") != configuration.end() &&
510 configuration["logging"] == "true" &&
511 (!shared->mWriter || !shared->mWriter->isOpened()))
513 cv::Size s = cv::Size((int) shared->m_capture->get(CV_CAP_PROP_FRAME_WIDTH),
514 (int) shared->m_capture->get(CV_CAP_PROP_FRAME_HEIGHT));
516 std::string codec = configuration["codec"];
518 if(codec.empty() || codec.size() != 4)
520 DebugOut(DebugOut::Warning)<<"Invalid codec. Using default: MJPG"<<endl;
524 std::string filename = configuration["logfile"];
526 if(filename.empty()) filename = "/tmp/video.avi";
528 boost::algorithm::to_upper(codec);
530 shared->mWriter = amb::make_unique(new cv::VideoWriter(filename, CV_FOURCC(codec.at(0), codec.at(1), codec.at(2), codec.at(3)),30,s));
533 DebugOut()<<"camera frame width: "<<shared->m_capture->get(CV_CAP_PROP_FRAME_WIDTH)<<endl;
534 DebugOut()<<"camera frame height: "<<shared->m_capture->get(CV_CAP_PROP_FRAME_HEIGHT)<<endl;
535 DebugOut()<<"camera frame fps: "<<shared->m_capture->get(CV_CAP_PROP_FPS)<<endl;
542 void OpenCvLuxPlugin::writeVideoFrame(cv::Mat f)
544 QMutexLocker locker(&mutex);
546 if(shared->loggingOn && speed > 0)
551 std::stringstream str;
552 str<<"Speed: "<<speed<<" kph, Lat/Lon: "<<latitude<<"/"<<longitude;
554 std::stringstream datestr;
555 datestr<<QDateTime::currentDateTime().toString().toStdString();
559 std::string text = str.str();
561 int fontFace = cv::FONT_HERSHEY_SIMPLEX;
562 double fontScale = 1;
566 cv::Size textSize = cv::getTextSize(text, fontFace,
567 fontScale, thickness, &baseline);
569 cv::Size dateTextSize = cv::getTextSize(datestr.str(), fontFace,
570 fontScale, thickness, &baseline);
572 baseline += thickness;
575 cv::Point textOrg((frame.cols - textSize.width)/2,
576 (frame.rows - textSize.height));
578 cv::Point dateOrg((frame.cols - dateTextSize.width)/2, dateTextSize.height);
580 // then put the text itself
581 cv::putText(frame, text, textOrg, fontFace, fontScale,
582 cv::Scalar::all(255), thickness, 8);
584 cv::putText(frame, datestr.str(), dateOrg, fontFace, fontScale,
585 cv::Scalar::all(255), thickness, 8);
587 (*shared->mWriter) << frame;
588 (*shared->mWriter) << frame;
592 void OpenCvLuxPlugin::updateProperty(uint lux)
594 extBrightness->setValue(lux);
596 for(auto reply : replyQueue)
598 reply->value = extBrightness.get();
599 reply->success = true;
602 reply->completed(reply);
606 DebugOut(DebugOut::Warning)<<"reply failed"<<endl;
612 if(lux != lastLux && shared->mRequests.size())
615 routingEngine->updateProperty(extBrightness.get(), uuid());
621 void OpenCvLuxPlugin::imgProcResult()
625 QFutureWatcher<uint> *watcher = dynamic_cast<QFutureWatcher<uint>*>(sender());
627 uint lux = watcher->result();
628 shared->parent->updateProperty(lux);
630 watcher->deleteLater();
634 DebugOut(DebugOut::Warning) << "exception caught getting img processing result" << endl;
638 TrafficLight::Color detectLight(cv::Mat img, OpenCvLuxPlugin::Shared *shared)
643 if(shared->useOpenCl)
646 cv::ocl::oclMat src(img), gray2;
647 cv::ocl::cvtColor(src, gray2, CV_BGR2GRAY);
648 cv::ocl::GaussianBlur(gray2, gray2, cv::Size(9,9), 2, 2);
655 cv::cvtColor(img, gray, CV_BGR2GRAY);
656 cv::GaussianBlur(gray, gray, cv::Size(9,9), 2, 2);
659 std::vector<cv::Vec3f> circles;
661 //cv::HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, gray.rows/8, 200, 100, 0, 0);
663 cv::HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 2, 30, 231, 50, 0, 0 );
665 for(auto i = 0; i < circles.size(); i++)
667 cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
668 int radius = cvRound(circles[i][2]);
670 cv::Rect rect(center.x - radius / 2, center.y - radius / 2, radius, radius);
674 cv::Mat light(img, rect);
676 cv::circle( img, center, radius, cv::Scalar(0,0,255), 3, 8, 0 );
678 cv::rectangle(img, rect,cv::Scalar(255,0,0));
680 cv::Scalar avgPixel = cv::mean(light);
682 if(avgPixel[2] > 128 && avgPixel[0] < 128 && avgPixel[1] < 128)
684 DebugOut(1)<<"Red Light!!!"<<endl;
685 return TrafficLight::Red;
687 else if(avgPixel[1] > 128 && avgPixel[0] < 128 && avgPixel[2] < 128)
689 DebugOut(1)<<"Green Light!!!"<<endl;
690 return TrafficLight::Green;
692 else if(avgPixel[0] > 128 && avgPixel[1] < 128 && avgPixel[2] < 128)
694 DebugOut(1)<<"Yellow Light!!!"<<endl;
695 return TrafficLight::Yellow;
705 cv::namedWindow( "Hough Circle Transform Demo", CV_WINDOW_AUTOSIZE );
706 cv::imshow( "Hough Circle Transform Demo", img );
710 void OpenCvLuxPlugin::detectEyes(cv::Mat frame)
715 bool hasEyes = false;
716 std::vector<cv::Rect> faces;
719 cv::ocl::oclMat gray2;
722 if(shared->useOpenCl)
725 cv::ocl::oclMat src(frame);
726 cv::ocl::cvtColor(src, gray2, CV_BGR2GRAY);
727 cv::ocl::equalizeHist(gray2, gray2);
728 cv::ocl::OclCascadeClassifier* fc = static_cast<cv::ocl::OclCascadeClassifier*>(faceCascade.get());
729 fc->detectMultiScale(gray2, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 30));
734 cv::cvtColor(frame, frameGray, CV_BGR2GRAY);
735 cv::equalizeHist(frameGray, frameGray);
736 faceCascade->detectMultiScale(frameGray, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 30));
739 for( size_t i = 0; i < faces.size(); i++ )
741 cv::Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
742 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 );
744 std::vector<cv::Rect> eyes;
746 if(shared->useOpenCl)
749 cv::ocl::oclMat faceROI = gray2(faces[i]);
750 cv::ocl::OclCascadeClassifier* ec = static_cast<cv::ocl::OclCascadeClassifier*>(eyeCascade.get());
751 ec->detectMultiScale( faceROI, eyes, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 30) );
756 cv::Mat faceROI = frameGray(faces[i]);
757 eyeCascade->detectMultiScale( faceROI, eyes, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, cv::Size(30, 30) );
763 DebugOut() << "Number of eyes: " << eyes.size() << endl;
766 for( size_t j = 0; j < eyes.size(); j++ )
768 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 );
769 int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
770 cv::circle( frame, center, radius, cv::Scalar( 255, 0, 0 ), 4, 8, 0 );
774 routingEngine->updateProperty(driverDrowsiness.get(),this->uuid());