#include #include #include #include "daemon/beanstalk.hpp" #include "video/logging_videobuffer.h" #include "tclap/CmdLine.h" #include "alpr.h" #include "openalpr/simpleini/simpleini.h" #include "openalpr/cjson.h" #include "support/tinythread.h" #include #include "support/timing.h" #include #include #include #include #include using namespace alpr; // prototypes void streamRecognitionThread(void* arg); bool writeToQueue(std::string jsonResult); bool uploadPost(std::string url, std::string data); void dataUploadThread(void* arg); // Constants const std::string ALPRD_CONFIG_FILE_NAME="alprd.conf"; const std::string OPENALPR_CONFIG_FILE_NAME="openalpr.conf"; const std::string DEFAULT_LOG_FILE_PATH="/var/log/alprd.log"; const std::string BEANSTALK_QUEUE_HOST="127.0.0.1"; const int BEANSTALK_PORT=11300; const std::string BEANSTALK_TUBE_NAME="alprd"; struct CaptureThreadData { std::string stream_url; std::string site_id; int camera_id; bool clock_on; std::string config_file; std::string country_code; bool output_images; std::string output_image_folder; int top_n; }; struct UploadThreadData { std::string upload_url; }; void segfault_handler(int sig) { void *array[10]; size_t size; // get void*'s for all entries on the stack size = backtrace(array, 10); // print out all the frames to stderr fprintf(stderr, "Error: signal %d:\n", sig); backtrace_symbols_fd(array, size, STDERR_FILENO); exit(1); } bool daemon_active; static log4cplus::Logger logger; int main( int argc, const char** argv ) { signal(SIGSEGV, segfault_handler); // install our segfault handler daemon_active = true; bool noDaemon = false; bool clockOn = false; std::string logFile; std::string configDir; TCLAP::CmdLine cmd("OpenAlpr Daemon", ' ', Alpr::getVersion()); TCLAP::ValueArg configDirArg("","config","Path to the openalpr config directory that contains alprd.conf and openalpr.conf. (Default: /etc/openalpr/)",false, "/etc/openalpr/" ,"config_file"); TCLAP::ValueArg logFileArg("l","log","Log file to write to. Default=" + DEFAULT_LOG_FILE_PATH,false, DEFAULT_LOG_FILE_PATH ,"topN"); TCLAP::SwitchArg daemonOffSwitch("f","foreground","Set this flag for debugging. Disables forking the process as a daemon and runs in the foreground. Default=off", cmd, false); TCLAP::SwitchArg clockSwitch("","clock","Display timing information to log. Default=off", cmd, false); try { cmd.add( configDirArg ); cmd.add( logFileArg ); if (cmd.parse( argc, argv ) == false) { // Error occured while parsing. Exit now. return 1; } // Make sure configDir ends in a slash configDir = configDirArg.getValue(); if (hasEnding(configDir, "/") == false) configDir = configDir + "/"; logFile = logFileArg.getValue(); noDaemon = daemonOffSwitch.getValue(); clockOn = clockSwitch.getValue(); } catch (TCLAP::ArgException &e) // catch any exceptions { std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; } std::string openAlprConfigFile = configDir + OPENALPR_CONFIG_FILE_NAME; std::string daemonConfigFile = configDir + ALPRD_CONFIG_FILE_NAME; // Validate that the configuration files exist if (fileExists(openAlprConfigFile.c_str()) == false) { std::cerr << "error, openalpr.conf file does not exist at: " << openAlprConfigFile << std::endl; return 1; } if (fileExists(daemonConfigFile.c_str()) == false) { std::cerr << "error, alprd.conf file does not exist at: " << daemonConfigFile << std::endl; return 1; } log4cplus::BasicConfigurator config; config.configure(); if (noDaemon == false) { // Fork off into a separate daemon daemon(0, 0); log4cplus::SharedAppenderPtr myAppender(new log4cplus::RollingFileAppender(logFile)); myAppender->setName("alprd_appender"); // Redirect std out to log file logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("alprd")); logger.addAppender(myAppender); LOG4CPLUS_INFO(logger, "Running OpenALPR daemon in daemon mode."); } else { //log4cplus::SharedAppenderPtr myAppender(new log4cplus::ConsoleAppender()); //myAppender->setName("alprd_appender"); // Redirect std out to log file logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("alprd")); //logger.addAppender(myAppender); LOG4CPLUS_INFO(logger, "Running OpenALPR daemon in the foreground."); } CSimpleIniA ini; ini.SetMultiKey(); ini.LoadFile(daemonConfigFile.c_str()); std::vector stream_urls; CSimpleIniA::TNamesDepend values; ini.GetAllValues("daemon", "stream", values); // sort the values into the original load order values.sort(CSimpleIniA::Entry::LoadOrder()); // output all of the items CSimpleIniA::TNamesDepend::const_iterator i; for (i = values.begin(); i != values.end(); ++i) { stream_urls.push_back(i->pItem); } if (stream_urls.size() == 0) { LOG4CPLUS_FATAL(logger, "No video streams defined in the configuration."); return 1; } std::string country = ini.GetValue("daemon", "country", "us"); int topn = ini.GetLongValue("daemon", "topn", 20); bool storePlates = ini.GetBoolValue("daemon", "store_plates", false); std::string imageFolder = ini.GetValue("daemon", "store_plates_location", "/tmp/"); bool uploadData = ini.GetBoolValue("daemon", "upload_data", false); std::string upload_url = ini.GetValue("daemon", "upload_address", ""); std::string site_id = ini.GetValue("daemon", "site_id", ""); LOG4CPLUS_INFO(logger, "Using: " << daemonConfigFile << " for daemon configuration"); LOG4CPLUS_INFO(logger, "Using: " << imageFolder << " for storing valid plate images"); pid_t pid; for (int i = 0; i < stream_urls.size(); i++) { pid = fork(); if (pid == (pid_t) 0) { // This is the child process, kick off the capture data and upload threads CaptureThreadData* tdata = new CaptureThreadData(); tdata->stream_url = stream_urls[i]; tdata->camera_id = i + 1; tdata->config_file = openAlprConfigFile; tdata->output_images = storePlates; tdata->output_image_folder = imageFolder; tdata->country_code = country; tdata->site_id = site_id; tdata->top_n = topn; tdata->clock_on = clockOn; tthread::thread* thread_recognize = new tthread::thread(streamRecognitionThread, (void*) tdata); if (uploadData) { // Kick off the data upload thread UploadThreadData* udata = new UploadThreadData(); udata->upload_url = upload_url; tthread::thread* thread_upload = new tthread::thread(dataUploadThread, (void*) udata ); } break; } // Parent process will continue and spawn more children } while (daemon_active) { usleep(30000); } } void streamRecognitionThread(void* arg) { CaptureThreadData* tdata = (CaptureThreadData*) arg; LOG4CPLUS_INFO(logger, "country: " << tdata->country_code << " -- config file: " << tdata->config_file ); LOG4CPLUS_INFO(logger, "Stream " << tdata->camera_id << ": " << tdata->stream_url); Alpr alpr(tdata->country_code, tdata->config_file); alpr.setTopN(tdata->top_n); int framenum = 0; LoggingVideoBuffer videoBuffer(logger); videoBuffer.connect(tdata->stream_url, 5); cv::Mat latestFrame; std::vector buffer; LOG4CPLUS_INFO(logger, "Starting camera " << tdata->camera_id); while (daemon_active) { std::vector regionsOfInterest; int response = videoBuffer.getLatestFrame(&latestFrame, regionsOfInterest); if (response != -1) { timespec startTime; getTime(&startTime); std::vector regionsOfInterest; regionsOfInterest.push_back(AlprRegionOfInterest(0,0, latestFrame.cols, latestFrame.rows)); AlprResults results = alpr.recognize(latestFrame.data, latestFrame.elemSize(), latestFrame.cols, latestFrame.rows, regionsOfInterest); timespec endTime; getTime(&endTime); double totalProcessingTime = diffclock(startTime, endTime); if (tdata->clock_on) { LOG4CPLUS_INFO(logger, "Camera " << tdata->camera_id << " processed frame in: " << totalProcessingTime << " ms."); } if (results.plates.size() > 0) { long epoch_time = getEpochTime(); std::stringstream uuid_ss; uuid_ss << tdata->site_id << "-cam" << tdata->camera_id << "-" << epoch_time; std::string uuid = uuid_ss.str(); // Save the image to disk (using the UUID) if (tdata->output_images) { std::stringstream ss; ss << tdata->output_image_folder << "/" << uuid << ".jpg"; cv::imwrite(ss.str(), latestFrame); } // Update the JSON content to include UUID and camera ID std::string json = alpr.toJson(results); cJSON *root = cJSON_Parse(json.c_str()); cJSON_AddStringToObject(root, "uuid", uuid.c_str()); cJSON_AddNumberToObject(root, "camera_id", tdata->camera_id); cJSON_AddStringToObject(root, "site_id", tdata->site_id.c_str()); cJSON_AddNumberToObject(root, "img_width", latestFrame.cols); cJSON_AddNumberToObject(root, "img_height", latestFrame.rows); char *out; out=cJSON_PrintUnformatted(root); cJSON_Delete(root); std::string response(out); free(out); // Push the results to the Beanstalk queue for (int j = 0; j < results.plates.size(); j++) { LOG4CPLUS_DEBUG(logger, "Writing plate " << results.plates[j].bestPlate.characters << " (" << uuid << ") to queue."); } writeToQueue(response); } } usleep(10000); } videoBuffer.disconnect(); LOG4CPLUS_INFO(logger, "Video processing ended"); delete tdata; } bool writeToQueue(std::string jsonResult) { try { Beanstalk::Client client(BEANSTALK_QUEUE_HOST, BEANSTALK_PORT); client.use(BEANSTALK_TUBE_NAME); int id = client.put(jsonResult); if (id <= 0) { LOG4CPLUS_ERROR(logger, "Failed to write data to queue"); return false; } LOG4CPLUS_DEBUG(logger, "put job id: " << id ); } catch (const std::runtime_error& error) { LOG4CPLUS_WARN(logger, "Error connecting to Beanstalk. Result has not been saved."); return false; } return true; } void dataUploadThread(void* arg) { /* In windows, this will init the winsock stuff */ curl_global_init(CURL_GLOBAL_ALL); UploadThreadData* udata = (UploadThreadData*) arg; while(daemon_active) { try { Beanstalk::Client client(BEANSTALK_QUEUE_HOST, BEANSTALK_PORT); client.watch(BEANSTALK_TUBE_NAME); while (daemon_active) { Beanstalk::Job job; client.reserve(job); if (job.id() > 0) { //LOG4CPLUS_DEBUG(logger, job.body() ); if (uploadPost(udata->upload_url, job.body())) { client.del(job.id()); LOG4CPLUS_INFO(logger, "Job: " << job.id() << " successfully uploaded" ); // Wait 10ms usleep(10000); } else { client.release(job); LOG4CPLUS_WARN(logger, "Job: " << job.id() << " failed to upload. Will retry." ); // Wait 2 seconds usleep(2000000); } } } } catch (const std::runtime_error& error) { LOG4CPLUS_WARN(logger, "Error connecting to Beanstalk. Will retry." ); } // wait 5 seconds usleep(5000000); } curl_global_cleanup(); } bool uploadPost(std::string url, std::string data) { bool success = true; CURL *curl; CURLcode res; struct curl_slist *headers=NULL; // init to NULL is important /* Add the required headers */ headers = curl_slist_append(headers, "Accept: application/json"); headers = curl_slist_append( headers, "Content-Type: application/json"); headers = curl_slist_append( headers, "charsets: utf-8"); /* get a curl handle */ curl = curl_easy_init(); if(curl) { /* Add the headers */ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); /* First set the URL that is about to receive our POST. This URL can just as well be a https:// URL if that is what should receive the data. */ curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); /* Now specify the POST data */ //char* escaped_data = curl_easy_escape(curl, data.c_str(), data.length()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); //curl_free(escaped_data); /* Perform the request, res will get the return code */ res = curl_easy_perform(curl); /* Check for errors */ if(res != CURLE_OK) { success = false; } /* always cleanup */ curl_easy_cleanup(curl); } return success; }