From 168af40fe9a3cc81c6ee16b3e81f154780c36bdb Mon Sep 17 00:00:00 2001 From: Scheaven <xuepengqiang> Date: 星期四, 03 六月 2021 15:03:27 +0800 Subject: [PATCH] up new v4 --- lib/detecter_tools/darknet/http_stream.cpp | 1697 ++++++++++++++++++++++++++++++++-------------------------- 1 files changed, 940 insertions(+), 757 deletions(-) diff --git a/lib/detecter_tools/darknet/http_stream.cpp b/lib/detecter_tools/darknet/http_stream.cpp index 081b61e..b17edfb 100644 --- a/lib/detecter_tools/darknet/http_stream.cpp +++ b/lib/detecter_tools/darknet/http_stream.cpp @@ -1,757 +1,940 @@ -#define _XOPEN_SOURCE -#include "image.h" -#include "http_stream.h" - -// -// a single-threaded, multi client(using select), debug webserver - streaming out mjpg. -// on win, _WIN32 has to be defined, must link against ws2_32.lib (socks on linux are for free) -// - -#include <cstdio> -#include <vector> -#include <iostream> -#include <algorithm> -#include <memory> -#include <mutex> -#include <thread> -#include <atomic> -#include <ctime> -using std::cerr; -using std::endl; - -// -// socket related abstractions: -// -#ifdef _WIN32 -#ifndef USE_CMAKE_LIBS -#pragma comment(lib, "ws2_32.lib") -#endif -#define WIN32_LEAN_AND_MEAN -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#include <windows.h> -#include <winsock2.h> -#include <ws2tcpip.h> -#include "gettimeofday.h" -#define PORT unsigned long -#define ADDRPOINTER int* -struct _INIT_W32DATA -{ - WSADATA w; - _INIT_W32DATA() { WSAStartup(MAKEWORD(2, 1), &w); } -} _init_once; - -// Graceful closes will first close their output channels and then wait for the peer -// on the other side of the connection to close its output channels. When both sides are done telling -// each other they won,t be sending any more data (i.e., closing output channels), -// the connection can be closed fully, with no risk of reset. -static int close_socket(SOCKET s) { - int close_output = ::shutdown(s, 1); // 0 close input, 1 close output, 2 close both - char *buf = (char *)calloc(1024, sizeof(char)); - ::recv(s, buf, 1024, 0); - free(buf); - int close_input = ::shutdown(s, 0); - int result = ::closesocket(s); - cerr << "Close socket: out = " << close_output << ", in = " << close_input << " \n"; - return result; -} -#else // _WIN32 - else: nix -#include "darkunistd.h" -#include <fcntl.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <signal.h> -#define PORT unsigned short -#define SOCKET int -#define HOSTENT struct hostent -#define SOCKADDR struct sockaddr -#define SOCKADDR_IN struct sockaddr_in -#define ADDRPOINTER unsigned int* -#define INVALID_SOCKET -1 -#define SOCKET_ERROR -1 -struct _IGNORE_PIPE_SIGNAL -{ - struct sigaction new_actn, old_actn; - _IGNORE_PIPE_SIGNAL() { - new_actn.sa_handler = SIG_IGN; // ignore the broken pipe signal - sigemptyset(&new_actn.sa_mask); - new_actn.sa_flags = 0; - sigaction(SIGPIPE, &new_actn, &old_actn); - // sigaction (SIGPIPE, &old_actn, NULL); // - to restore the previous signal handling - } -} _init_once; - -static int close_socket(SOCKET s) { - int close_output = ::shutdown(s, 1); // 0 close input, 1 close output, 2 close both - char *buf = (char *)calloc(1024, sizeof(char)); - ::recv(s, buf, 1024, 0); - free(buf); - int close_input = ::shutdown(s, 0); - int result = close(s); - std::cerr << "Close socket: out = " << close_output << ", in = " << close_input << " \n"; - return result; -} -#endif // _WIN32 - - -class JSON_sender -{ - SOCKET sock; - SOCKET maxfd; - fd_set master; - int timeout; // master sock timeout, shutdown after timeout usec. - int close_all_sockets; - - int _write(int sock, char const*const s, int len) - { - if (len < 1) { len = strlen(s); } - return ::send(sock, s, len, 0); - } - -public: - - JSON_sender(int port = 0, int _timeout = 400000) - : sock(INVALID_SOCKET) - , timeout(_timeout) - { - close_all_sockets = 0; - FD_ZERO(&master); - if (port) - open(port); - } - - ~JSON_sender() - { - close_all(); - release(); - } - - bool release() - { - if (sock != INVALID_SOCKET) - ::shutdown(sock, 2); - sock = (INVALID_SOCKET); - return false; - } - - void close_all() - { - close_all_sockets = 1; - write("\n]"); // close JSON array - } - - bool open(int port) - { - sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - SOCKADDR_IN address; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_family = AF_INET; - address.sin_port = htons(port); // ::htons(port); - int reuse = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) - cerr << "setsockopt(SO_REUSEADDR) failed" << endl; - - // Non-blocking sockets - // Windows: ioctlsocket() and FIONBIO - // Linux: fcntl() and O_NONBLOCK -#ifdef WIN32 - unsigned long i_mode = 1; - int result = ioctlsocket(sock, FIONBIO, &i_mode); - if (result != NO_ERROR) { - std::cerr << "ioctlsocket(FIONBIO) failed with error: " << result << std::endl; - } -#else // WIN32 - int flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, flags | O_NONBLOCK); -#endif // WIN32 - -#ifdef SO_REUSEPORT - if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) - cerr << "setsockopt(SO_REUSEPORT) failed" << endl; -#endif - if (::bind(sock, (SOCKADDR*)&address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) - { - cerr << "error JSON_sender: couldn't bind sock " << sock << " to port " << port << "!" << endl; - return release(); - } - if (::listen(sock, 10) == SOCKET_ERROR) - { - cerr << "error JSON_sender: couldn't listen on sock " << sock << " on port " << port << " !" << endl; - return release(); - } - FD_ZERO(&master); - FD_SET(sock, &master); - maxfd = sock; - return true; - } - - bool isOpened() - { - return sock != INVALID_SOCKET; - } - - bool write(char const* outputbuf) - { - fd_set rread = master; - struct timeval select_timeout = { 0, 0 }; - struct timeval socket_timeout = { 0, timeout }; - if (::select(maxfd + 1, &rread, NULL, NULL, &select_timeout) <= 0) - return true; // nothing broken, there's just noone listening - - int outlen = static_cast<int>(strlen(outputbuf)); - -#ifdef _WIN32 - for (unsigned i = 0; i<rread.fd_count; i++) - { - int addrlen = sizeof(SOCKADDR); - SOCKET s = rread.fd_array[i]; // fd_set on win is an array, while ... -#else - for (int s = 0; s <= maxfd; s++) - { - socklen_t addrlen = sizeof(SOCKADDR); - if (!FD_ISSET(s, &rread)) // ... on linux it's a bitmask ;) - continue; -#endif - if (s == sock) // request on master socket, accept and send main header. - { - SOCKADDR_IN address = { 0 }; - SOCKET client = ::accept(sock, (SOCKADDR*)&address, &addrlen); - if (client == SOCKET_ERROR) - { - cerr << "error JSON_sender: couldn't accept connection on sock " << sock << " !" << endl; - return false; - } - if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { - cerr << "error JSON_sender: SO_RCVTIMEO setsockopt failed\n"; - } - if (setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { - cerr << "error JSON_sender: SO_SNDTIMEO setsockopt failed\n"; - } - maxfd = (maxfd>client ? maxfd : client); - FD_SET(client, &master); - _write(client, "HTTP/1.0 200 OK\r\n", 0); - _write(client, - "Server: Mozarella/2.2\r\n" - "Accept-Range: bytes\r\n" - "Connection: close\r\n" - "Max-Age: 0\r\n" - "Expires: 0\r\n" - "Cache-Control: no-cache, private\r\n" - "Pragma: no-cache\r\n" - "Content-Type: application/json\r\n" - //"Content-Type: multipart/x-mixed-replace; boundary=boundary\r\n" - "\r\n", 0); - _write(client, "[\n", 0); // open JSON array - int n = _write(client, outputbuf, outlen); - cerr << "JSON_sender: new client " << client << endl; - } - else // existing client, just stream pix - { - //char head[400]; - // application/x-resource+json or application/x-collection+json - when you are representing REST resources and collections - // application/json or text/json or text/javascript or text/plain. - // https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type - //sprintf(head, "\r\nContent-Length: %zu\r\n\r\n", outlen); - //sprintf(head, "--boundary\r\nContent-Type: application/json\r\nContent-Length: %zu\r\n\r\n", outlen); - //_write(s, head, 0); - if (!close_all_sockets) _write(s, ", \n", 0); - int n = _write(s, outputbuf, outlen); - if (n < (int)outlen) - { - cerr << "JSON_sender: kill client " << s << endl; - close_socket(s); - //::shutdown(s, 2); - FD_CLR(s, &master); - } - - if (close_all_sockets) { - int result = close_socket(s); - cerr << "JSON_sender: close clinet: " << result << " \n"; - continue; - } - } - } - if (close_all_sockets) { - int result = close_socket(sock); - cerr << "JSON_sender: close acceptor: " << result << " \n\n"; - } - return true; - } -}; -// ---------------------------------------- - -static std::unique_ptr<JSON_sender> js_ptr; -static std::mutex mtx; - -void delete_json_sender() -{ - std::lock_guard<std::mutex> lock(mtx); - js_ptr.release(); -} - -void send_json_custom(char const* send_buf, int port, int timeout) -{ - try { - std::lock_guard<std::mutex> lock(mtx); - if(!js_ptr) js_ptr.reset(new JSON_sender(port, timeout)); - - js_ptr->write(send_buf); - } - catch (...) { - cerr << " Error in send_json_custom() function \n"; - } -} - -void send_json(detection *dets, int nboxes, int classes, char **names, long long int frame_id, int port, int timeout) -{ - try { - char *send_buf = detection_to_json(dets, nboxes, classes, names, frame_id, NULL); - - send_json_custom(send_buf, port, timeout); - std::cout << " JSON-stream sent. \n"; - - free(send_buf); - } - catch (...) { - cerr << " Error in send_json() function \n"; - } -} -// ---------------------------------------- - - -#ifdef OPENCV - -#include <opencv2/opencv.hpp> -#include <opencv2/highgui/highgui.hpp> -#include <opencv2/highgui/highgui_c.h> -#include <opencv2/imgproc/imgproc_c.h> -#ifndef CV_VERSION_EPOCH -#include <opencv2/videoio/videoio.hpp> -#endif -using namespace cv; - - - -class MJPG_sender -{ - SOCKET sock; - SOCKET maxfd; - fd_set master; - int timeout; // master sock timeout, shutdown after timeout usec. - int quality; // jpeg compression [1..100] - int close_all_sockets; - - int _write(int sock, char const*const s, int len) - { - if (len < 1) { len = strlen(s); } - return ::send(sock, s, len, 0); - } - -public: - - MJPG_sender(int port = 0, int _timeout = 400000, int _quality = 30) - : sock(INVALID_SOCKET) - , timeout(_timeout) - , quality(_quality) - { - close_all_sockets = 0; - FD_ZERO(&master); - if (port) - open(port); - } - - ~MJPG_sender() - { - close_all(); - release(); - } - - bool release() - { - if (sock != INVALID_SOCKET) - ::shutdown(sock, 2); - sock = (INVALID_SOCKET); - return false; - } - - void close_all() - { - close_all_sockets = 1; - cv::Mat tmp(cv::Size(10, 10), CV_8UC3); - write(tmp); - } - - bool open(int port) - { - sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - SOCKADDR_IN address; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_family = AF_INET; - address.sin_port = htons(port); // ::htons(port); - int reuse = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) - cerr << "setsockopt(SO_REUSEADDR) failed" << endl; - - // Non-blocking sockets - // Windows: ioctlsocket() and FIONBIO - // Linux: fcntl() and O_NONBLOCK -#ifdef WIN32 - unsigned long i_mode = 1; - int result = ioctlsocket(sock, FIONBIO, &i_mode); - if (result != NO_ERROR) { - std::cerr << "ioctlsocket(FIONBIO) failed with error: " << result << std::endl; - } -#else // WIN32 - int flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, flags | O_NONBLOCK); -#endif // WIN32 - -#ifdef SO_REUSEPORT - if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) - cerr << "setsockopt(SO_REUSEPORT) failed" << endl; -#endif - if (::bind(sock, (SOCKADDR*)&address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) - { - cerr << "error MJPG_sender: couldn't bind sock " << sock << " to port " << port << "!" << endl; - return release(); - } - if (::listen(sock, 10) == SOCKET_ERROR) - { - cerr << "error MJPG_sender: couldn't listen on sock " << sock << " on port " << port << " !" << endl; - return release(); - } - FD_ZERO(&master); - FD_SET(sock, &master); - maxfd = sock; - return true; - } - - bool isOpened() - { - return sock != INVALID_SOCKET; - } - - bool write(const Mat & frame) - { - fd_set rread = master; - struct timeval select_timeout = { 0, 0 }; - struct timeval socket_timeout = { 0, timeout }; - if (::select(maxfd + 1, &rread, NULL, NULL, &select_timeout) <= 0) - return true; // nothing broken, there's just noone listening - - std::vector<uchar> outbuf; - std::vector<int> params; - params.push_back(IMWRITE_JPEG_QUALITY); - params.push_back(quality); - cv::imencode(".jpg", frame, outbuf, params); //REMOVED FOR COMPATIBILITY - // https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html#ga292d81be8d76901bff7988d18d2b42ac - //std::cerr << "cv::imencode call disabled!" << std::endl; - int outlen = static_cast<int>(outbuf.size()); - -#ifdef _WIN32 - for (unsigned i = 0; i<rread.fd_count; i++) - { - int addrlen = sizeof(SOCKADDR); - SOCKET s = rread.fd_array[i]; // fd_set on win is an array, while ... -#else - for (int s = 0; s <= maxfd; s++) - { - socklen_t addrlen = sizeof(SOCKADDR); - if (!FD_ISSET(s, &rread)) // ... on linux it's a bitmask ;) - continue; -#endif - if (s == sock) // request on master socket, accept and send main header. - { - SOCKADDR_IN address = { 0 }; - SOCKET client = ::accept(sock, (SOCKADDR*)&address, &addrlen); - if (client == SOCKET_ERROR) - { - cerr << "error MJPG_sender: couldn't accept connection on sock " << sock << " !" << endl; - return false; - } - if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { - cerr << "error MJPG_sender: SO_RCVTIMEO setsockopt failed\n"; - } - if (setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { - cerr << "error MJPG_sender: SO_SNDTIMEO setsockopt failed\n"; - } - maxfd = (maxfd>client ? maxfd : client); - FD_SET(client, &master); - _write(client, "HTTP/1.0 200 OK\r\n", 0); - _write(client, - "Server: Mozarella/2.2\r\n" - "Accept-Range: bytes\r\n" - "Connection: close\r\n" - "Max-Age: 0\r\n" - "Expires: 0\r\n" - "Cache-Control: no-cache, private\r\n" - "Pragma: no-cache\r\n" - "Content-Type: multipart/x-mixed-replace; boundary=mjpegstream\r\n" - "\r\n", 0); - cerr << "MJPG_sender: new client " << client << endl; - } - else // existing client, just stream pix - { - if (close_all_sockets) { - int result = close_socket(s); - cerr << "MJPG_sender: close clinet: " << result << " \n"; - continue; - } - - char head[400]; - sprintf(head, "--mjpegstream\r\nContent-Type: image/jpeg\r\nContent-Length: %zu\r\n\r\n", outlen); - _write(s, head, 0); - int n = _write(s, (char*)(&outbuf[0]), outlen); - cerr << "known client: " << s << ", sent = " << n << ", must be sent outlen = " << outlen << endl; - if (n < (int)outlen) - { - cerr << "MJPG_sender: kill client " << s << endl; - //::shutdown(s, 2); - close_socket(s); - FD_CLR(s, &master); - } - } - } - if (close_all_sockets) { - int result = close_socket(sock); - cerr << "MJPG_sender: close acceptor: " << result << " \n\n"; - } - return true; - } -}; -// ---------------------------------------- - -static std::mutex mtx_mjpeg; - -//struct mat_cv : cv::Mat { int a[0]; }; - -void send_mjpeg(mat_cv* mat, int port, int timeout, int quality) -{ - try { - std::lock_guard<std::mutex> lock(mtx_mjpeg); - static MJPG_sender wri(port, timeout, quality); - //cv::Mat mat = cv::cvarrToMat(ipl); - wri.write(*(cv::Mat*)mat); - std::cout << " MJPEG-stream sent. \n"; - } - catch (...) { - cerr << " Error in send_mjpeg() function \n"; - } -} -// ---------------------------------------- - -std::string get_system_frame_time_string() -{ - std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - static std::mutex mtx; - std::lock_guard<std::mutex> lock(mtx); - struct tm *tmp_buf = localtime(&t); - char buff[256]; - std::strftime(buff, 256, "%A %F %T", tmp_buf); - std::string system_frame_time = buff; - return system_frame_time; -} -// ---------------------------------------- - - -#ifdef __CYGWIN__ -int send_http_post_request(char *http_post_host, int server_port, const char *videosource, - detection *dets, int nboxes, int classes, char **names, long long int frame_id, int ext_output, int timeout) -{ - std::cerr << " send_http_post_request() isn't implemented \n"; - return 0; -} -#else // __CYGWIN__ - -#ifndef NI_MAXHOST -#define NI_MAXHOST 1025 -#endif - -#ifndef NI_NUMERICHOST -#define NI_NUMERICHOST 0x02 -#endif - -//#define CPPHTTPLIB_OPENSSL_SUPPORT -#include "httplib.h" - -// https://webhook.site/ -// https://github.com/yhirose/cpp-httplib -// sent POST http request -int send_http_post_request(char *http_post_host, int server_port, const char *videosource, - detection *dets, int nboxes, int classes, char **names, long long int frame_id, int ext_output, int timeout) -{ - const float thresh = 0.005; // function get_network_boxes() has already filtred dets by actual threshold - - std::string message; - - for (int i = 0; i < nboxes; ++i) { - char labelstr[4096] = { 0 }; - int class_id = -1; - for (int j = 0; j < classes; ++j) { - int show = strncmp(names[j], "dont_show", 9); - if (dets[i].prob[j] > thresh && show) { - if (class_id < 0) { - strcat(labelstr, names[j]); - class_id = j; - char buff[10]; - sprintf(buff, " (%2.0f%%)", dets[i].prob[j] * 100); - strcat(labelstr, buff); - } - else { - strcat(labelstr, ", "); - strcat(labelstr, names[j]); - } - printf("%s: %.0f%% ", names[j], dets[i].prob[j] * 100); - } - } - if (class_id >= 0) { - message += std::string(names[class_id]) + std::string(", id: ") + std::to_string(class_id) + "\n"; - } - } - - if (!message.empty()) - { - std::string time = get_system_frame_time_string(); - message += "\ntime:\n" + time + "\n"; - message += "videosource:\n" + std::string(videosource); - - std::string http_post_host_str = http_post_host; - int slash_index = http_post_host_str.find("/"); - - std::string http_path = http_post_host_str.substr(slash_index, http_post_host_str.length() - slash_index); - http_post_host_str = http_post_host_str.substr(0, slash_index); - - // send HTTP-Post request - httplib::Client cli(http_post_host_str.c_str(), server_port, timeout); - auto res = cli.Post(http_path.c_str(), message, "text/plain"); - - return 1; - } - - return 0; -} -#endif // __CYGWIN__ - -#endif // OPENCV - -// ----------------------------------------------------- - -#if __cplusplus >= 201103L || _MSC_VER >= 1900 // C++11 - -#include <chrono> -#include <iostream> - -static std::chrono::steady_clock::time_point steady_start, steady_end; -static double total_time; - -double get_time_point() { - std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); - //uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(current_time.time_since_epoch()).count(); - return std::chrono::duration_cast<std::chrono::microseconds>(current_time.time_since_epoch()).count(); -} - -void start_timer() { - steady_start = std::chrono::steady_clock::now(); -} - -void stop_timer() { - steady_end = std::chrono::steady_clock::now(); -} - -double get_time() { - double took_time = std::chrono::duration<double>(steady_end - steady_start).count(); - total_time += took_time; - return took_time; -} - -void stop_timer_and_show() { - stop_timer(); - std::cout << " " << get_time() * 1000 << " msec" << std::endl; -} - -void stop_timer_and_show_name(char *name) { - stop_timer(); - std::cout << " " << name; - std::cout << " " << get_time() * 1000 << " msec" << std::endl; -} - -void show_total_time() { - std::cout << " Total: " << total_time * 1000 << " msec" << std::endl; -} - - -int custom_create_thread(custom_thread_t * tid, const custom_attr_t * attr, void *(*func) (void *), void *arg) -{ - std::thread *ptr = new std::thread(func, arg); - *tid = (custom_thread_t *)ptr; - if (tid) return 0; - else return -1; -} - -int custom_join(custom_thread_t tid, void **value_ptr) -{ - std::thread *ptr = (std::thread *)tid; - if (ptr) { - ptr->join(); - delete ptr; - return 0; - } - else printf(" Error: ptr of thread is NULL in custom_join() \n"); - - return -1; -} - -int custom_atomic_load_int(volatile int* obj) -{ - const volatile std::atomic<int>* ptr_a = (const volatile std::atomic<int>*)obj; - return std::atomic_load(ptr_a); -} - -void custom_atomic_store_int(volatile int* obj, int desr) -{ - volatile std::atomic<int>* ptr_a = (volatile std::atomic<int>*)obj; - std::atomic_store(ptr_a, desr); -} - -int get_num_threads() -{ - return std::thread::hardware_concurrency(); -} - -#if !defined(__MINGW64__) -void this_thread_sleep_for(int ms_time) -{ - std::chrono::milliseconds dura(ms_time); - std::this_thread::sleep_for(dura); -} -#else -void this_thread_sleep_for(int ms_time) -{ - std::cerr << " this_thread_sleep_for() isn't implemented \n"; - return; -} -#endif - -void this_thread_yield() -{ - std::this_thread::yield(); -} - -#else // C++11 -#include <iostream> - -double get_time_point() { return 0; } -void start_timer() {} -void stop_timer() {} -double get_time() { return 0; } -void stop_timer_and_show() { - std::cout << " stop_timer_and_show() isn't implemented " << std::endl; -} -void stop_timer_and_show_name(char *name) { stop_timer_and_show(); } -void total_time() {} -#endif // C++11 +#define _XOPEN_SOURCE +#include "image.h" +#include "http_stream.h" + +// +// a single-threaded, multi client(using select), debug webserver - streaming out mjpg. +// on win, _WIN32 has to be defined, must link against ws2_32.lib (socks on linux are for free) +// + +#include <cstdio> +#include <vector> +#include <iostream> +#include <algorithm> +#include <memory> +#include <mutex> +#include <thread> +#include <atomic> +#include <ctime> +using std::cerr; +using std::endl; + +// +// socket related abstractions: +// +#ifdef _WIN32 +#ifndef USE_CMAKE_LIBS +#pragma comment(lib, "ws2_32.lib") +#endif +#define WIN32_LEAN_AND_MEAN +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include <windows.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#include "gettimeofday.h" +#define PORT unsigned long +#define ADDRPOINTER int* +struct _INIT_W32DATA +{ + WSADATA w; + _INIT_W32DATA() { WSAStartup(MAKEWORD(2, 1), &w); } +} _init_once; + +// Graceful closes will first close their output channels and then wait for the peer +// on the other side of the connection to close its output channels. When both sides are done telling +// each other they won,t be sending any more data (i.e., closing output channels), +// the connection can be closed fully, with no risk of reset. +static int close_socket(SOCKET s) { + int close_output = ::shutdown(s, 1); // 0 close input, 1 close output, 2 close both + char *buf = (char *)calloc(1024, sizeof(char)); + ::recv(s, buf, 1024, 0); + free(buf); + int close_input = ::shutdown(s, 0); + int result = ::closesocket(s); + cerr << "Close socket: out = " << close_output << ", in = " << close_input << " \n"; + return result; +} +#else // _WIN32 - else: nix +#include "darkunistd.h" +#include <fcntl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <signal.h> +#define PORT unsigned short +#define SOCKET int +#define HOSTENT struct hostent +#define SOCKADDR struct sockaddr +#define SOCKADDR_IN struct sockaddr_in +#define ADDRPOINTER unsigned int* +#ifndef INVALID_SOCKET +#define INVALID_SOCKET -1 +#endif +#ifndef SOCKET_ERROR +#define SOCKET_ERROR -1 +#endif +struct _IGNORE_PIPE_SIGNAL +{ + struct sigaction new_actn, old_actn; + _IGNORE_PIPE_SIGNAL() { + new_actn.sa_handler = SIG_IGN; // ignore the broken pipe signal + sigemptyset(&new_actn.sa_mask); + new_actn.sa_flags = 0; + sigaction(SIGPIPE, &new_actn, &old_actn); + // sigaction (SIGPIPE, &old_actn, NULL); // - to restore the previous signal handling + } +} _init_once; + +static int close_socket(SOCKET s) { + int close_output = ::shutdown(s, 1); // 0 close input, 1 close output, 2 close both + char *buf = (char *)calloc(1024, sizeof(char)); + ::recv(s, buf, 1024, 0); + free(buf); + int close_input = ::shutdown(s, 0); + int result = close(s); + std::cerr << "Close socket: out = " << close_output << ", in = " << close_input << " \n"; + return result; +} +#endif // _WIN32 + + +class JSON_sender +{ + SOCKET sock; + SOCKET maxfd; + fd_set master; + int timeout; // master sock timeout, shutdown after timeout usec. + int close_all_sockets; + + int _write(int sock, char const*const s, int len) + { + if (len < 1) { len = strlen(s); } + return ::send(sock, s, len, 0); + } + +public: + + JSON_sender(int port = 0, int _timeout = 400000) + : sock(INVALID_SOCKET) + , timeout(_timeout) + { + close_all_sockets = 0; + FD_ZERO(&master); + if (port) + open(port); + } + + ~JSON_sender() + { + close_all(); + release(); + } + + bool release() + { + if (sock != INVALID_SOCKET) + ::shutdown(sock, 2); + sock = (INVALID_SOCKET); + return false; + } + + void close_all() + { + close_all_sockets = 1; + write("\n]"); // close JSON array + } + + bool open(int port) + { + sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + SOCKADDR_IN address; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_family = AF_INET; + address.sin_port = htons(port); // ::htons(port); + int reuse = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) + cerr << "setsockopt(SO_REUSEADDR) failed" << endl; + + // Non-blocking sockets + // Windows: ioctlsocket() and FIONBIO + // Linux: fcntl() and O_NONBLOCK +#ifdef WIN32 + unsigned long i_mode = 1; + int result = ioctlsocket(sock, FIONBIO, &i_mode); + if (result != NO_ERROR) { + std::cerr << "ioctlsocket(FIONBIO) failed with error: " << result << std::endl; + } +#else // WIN32 + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif // WIN32 + +#ifdef SO_REUSEPORT + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) + cerr << "setsockopt(SO_REUSEPORT) failed" << endl; +#endif + if (::bind(sock, (SOCKADDR*)&address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) + { + cerr << "error JSON_sender: couldn't bind sock " << sock << " to port " << port << "!" << endl; + return release(); + } + if (::listen(sock, 10) == SOCKET_ERROR) + { + cerr << "error JSON_sender: couldn't listen on sock " << sock << " on port " << port << " !" << endl; + return release(); + } + FD_ZERO(&master); + FD_SET(sock, &master); + maxfd = sock; + return true; + } + + bool isOpened() + { + return sock != INVALID_SOCKET; + } + + bool write(char const* outputbuf) + { + fd_set rread = master; + struct timeval select_timeout = { 0, 0 }; + struct timeval socket_timeout = { 0, timeout }; + if (::select(maxfd + 1, &rread, NULL, NULL, &select_timeout) <= 0) + return true; // nothing broken, there's just noone listening + + int outlen = static_cast<int>(strlen(outputbuf)); + +#ifdef _WIN32 + for (unsigned i = 0; i<rread.fd_count; i++) + { + int addrlen = sizeof(SOCKADDR); + SOCKET s = rread.fd_array[i]; // fd_set on win is an array, while ... +#else + for (int s = 0; s <= maxfd; s++) + { + socklen_t addrlen = sizeof(SOCKADDR); + if (!FD_ISSET(s, &rread)) // ... on linux it's a bitmask ;) + continue; +#endif + if (s == sock) // request on master socket, accept and send main header. + { + SOCKADDR_IN address = { 0 }; + SOCKET client = ::accept(sock, (SOCKADDR*)&address, &addrlen); + if (client == SOCKET_ERROR) + { + cerr << "error JSON_sender: couldn't accept connection on sock " << sock << " !" << endl; + return false; + } + if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { + cerr << "error JSON_sender: SO_RCVTIMEO setsockopt failed\n"; + } + if (setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { + cerr << "error JSON_sender: SO_SNDTIMEO setsockopt failed\n"; + } + maxfd = (maxfd>client ? maxfd : client); + FD_SET(client, &master); + _write(client, "HTTP/1.0 200 OK\r\n", 0); + _write(client, + "Server: Mozarella/2.2\r\n" + "Accept-Range: bytes\r\n" + "Connection: close\r\n" + "Max-Age: 0\r\n" + "Expires: 0\r\n" + "Cache-Control: no-cache, private\r\n" + "Pragma: no-cache\r\n" + "Content-Type: application/json\r\n" + //"Content-Type: multipart/x-mixed-replace; boundary=boundary\r\n" + "\r\n", 0); + _write(client, "[\n", 0); // open JSON array + int n = _write(client, outputbuf, outlen); + cerr << "JSON_sender: new client " << client << endl; + } + else // existing client, just stream pix + { + //char head[400]; + // application/x-resource+json or application/x-collection+json - when you are representing REST resources and collections + // application/json or text/json or text/javascript or text/plain. + // https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type + //sprintf(head, "\r\nContent-Length: %zu\r\n\r\n", outlen); + //sprintf(head, "--boundary\r\nContent-Type: application/json\r\nContent-Length: %zu\r\n\r\n", outlen); + //_write(s, head, 0); + if (!close_all_sockets) _write(s, ", \n", 0); + int n = _write(s, outputbuf, outlen); + if (n < (int)outlen) + { + cerr << "JSON_sender: kill client " << s << endl; + close_socket(s); + //::shutdown(s, 2); + FD_CLR(s, &master); + } + + if (close_all_sockets) { + int result = close_socket(s); + cerr << "JSON_sender: close clinet: " << result << " \n"; + continue; + } + } + } + if (close_all_sockets) { + int result = close_socket(sock); + cerr << "JSON_sender: close acceptor: " << result << " \n\n"; + } + return true; + } +}; +// ---------------------------------------- + +static std::unique_ptr<JSON_sender> js_ptr; +static std::mutex mtx; + +void delete_json_sender() +{ + std::lock_guard<std::mutex> lock(mtx); + js_ptr.release(); +} + +void send_json_custom(char const* send_buf, int port, int timeout) +{ + try { + std::lock_guard<std::mutex> lock(mtx); + if(!js_ptr) js_ptr.reset(new JSON_sender(port, timeout)); + + js_ptr->write(send_buf); + } + catch (...) { + cerr << " Error in send_json_custom() function \n"; + } +} + +void send_json(detection *dets, int nboxes, int classes, char **names, long long int frame_id, int port, int timeout) +{ + try { + char *send_buf = detection_to_json(dets, nboxes, classes, names, frame_id, NULL); + + send_json_custom(send_buf, port, timeout); + std::cout << " JSON-stream sent. \n"; + + free(send_buf); + } + catch (...) { + cerr << " Error in send_json() function \n"; + } +} +// ---------------------------------------- + + +#ifdef OPENCV + +#include <opencv2/opencv.hpp> +#include <opencv2/highgui/highgui.hpp> +#include <opencv2/highgui/highgui_c.h> +#include <opencv2/imgproc/imgproc_c.h> +#ifndef CV_VERSION_EPOCH +#include <opencv2/videoio/videoio.hpp> +#endif +using namespace cv; + + + +class MJPG_sender +{ + SOCKET sock; + SOCKET maxfd; + fd_set master; + int timeout; // master sock timeout, shutdown after timeout usec. + int quality; // jpeg compression [1..100] + int close_all_sockets; + + int _write(int sock, char const*const s, int len) + { + if (len < 1) { len = strlen(s); } + return ::send(sock, s, len, 0); + } + +public: + + MJPG_sender(int port = 0, int _timeout = 400000, int _quality = 30) + : sock(INVALID_SOCKET) + , timeout(_timeout) + , quality(_quality) + { + close_all_sockets = 0; + FD_ZERO(&master); + if (port) + open(port); + } + + ~MJPG_sender() + { + close_all(); + release(); + } + + bool release() + { + if (sock != INVALID_SOCKET) + ::shutdown(sock, 2); + sock = (INVALID_SOCKET); + return false; + } + + void close_all() + { + close_all_sockets = 1; + cv::Mat tmp(cv::Size(10, 10), CV_8UC3); + write(tmp); + } + + bool open(int port) + { + sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + SOCKADDR_IN address; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_family = AF_INET; + address.sin_port = htons(port); // ::htons(port); + int reuse = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) + cerr << "setsockopt(SO_REUSEADDR) failed" << endl; + + // Non-blocking sockets + // Windows: ioctlsocket() and FIONBIO + // Linux: fcntl() and O_NONBLOCK +#ifdef WIN32 + unsigned long i_mode = 1; + int result = ioctlsocket(sock, FIONBIO, &i_mode); + if (result != NO_ERROR) { + std::cerr << "ioctlsocket(FIONBIO) failed with error: " << result << std::endl; + } +#else // WIN32 + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif // WIN32 + +#ifdef SO_REUSEPORT + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) + cerr << "setsockopt(SO_REUSEPORT) failed" << endl; +#endif + if (::bind(sock, (SOCKADDR*)&address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) + { + cerr << "error MJPG_sender: couldn't bind sock " << sock << " to port " << port << "!" << endl; + return release(); + } + if (::listen(sock, 10) == SOCKET_ERROR) + { + cerr << "error MJPG_sender: couldn't listen on sock " << sock << " on port " << port << " !" << endl; + return release(); + } + FD_ZERO(&master); + FD_SET(sock, &master); + maxfd = sock; + return true; + } + + bool isOpened() + { + return sock != INVALID_SOCKET; + } + + bool write(const Mat & frame) + { + fd_set rread = master; + struct timeval select_timeout = { 0, 0 }; + struct timeval socket_timeout = { 0, timeout }; + if (::select(maxfd + 1, &rread, NULL, NULL, &select_timeout) <= 0) + return true; // nothing broken, there's just noone listening + + std::vector<uchar> outbuf; + std::vector<int> params; + params.push_back(IMWRITE_JPEG_QUALITY); + params.push_back(quality); + cv::imencode(".jpg", frame, outbuf, params); //REMOVED FOR COMPATIBILITY + // https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html#ga292d81be8d76901bff7988d18d2b42ac + //std::cerr << "cv::imencode call disabled!" << std::endl; + int outlen = static_cast<int>(outbuf.size()); + +#ifdef _WIN32 + for (unsigned i = 0; i<rread.fd_count; i++) + { + int addrlen = sizeof(SOCKADDR); + SOCKET s = rread.fd_array[i]; // fd_set on win is an array, while ... +#else + for (int s = 0; s <= maxfd; s++) + { + socklen_t addrlen = sizeof(SOCKADDR); + if (!FD_ISSET(s, &rread)) // ... on linux it's a bitmask ;) + continue; +#endif + if (s == sock) // request on master socket, accept and send main header. + { + SOCKADDR_IN address = { 0 }; + SOCKET client = ::accept(sock, (SOCKADDR*)&address, &addrlen); + if (client == SOCKET_ERROR) + { + cerr << "error MJPG_sender: couldn't accept connection on sock " << sock << " !" << endl; + return false; + } + if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { + cerr << "error MJPG_sender: SO_RCVTIMEO setsockopt failed\n"; + } + if (setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, (char *)&socket_timeout, sizeof(socket_timeout)) < 0) { + cerr << "error MJPG_sender: SO_SNDTIMEO setsockopt failed\n"; + } + maxfd = (maxfd>client ? maxfd : client); + FD_SET(client, &master); + _write(client, "HTTP/1.0 200 OK\r\n", 0); + _write(client, + "Server: Mozarella/2.2\r\n" + "Accept-Range: bytes\r\n" + "Connection: close\r\n" + "Max-Age: 0\r\n" + "Expires: 0\r\n" + "Cache-Control: no-cache, private\r\n" + "Pragma: no-cache\r\n" + "Content-Type: multipart/x-mixed-replace; boundary=mjpegstream\r\n" + "\r\n", 0); + cerr << "MJPG_sender: new client " << client << endl; + } + else // existing client, just stream pix + { + if (close_all_sockets) { + int result = close_socket(s); + cerr << "MJPG_sender: close clinet: " << result << " \n"; + continue; + } + + char head[400]; + sprintf(head, "--mjpegstream\r\nContent-Type: image/jpeg\r\nContent-Length: %zu\r\n\r\n", outlen); + _write(s, head, 0); + int n = _write(s, (char*)(&outbuf[0]), outlen); + cerr << "known client: " << s << ", sent = " << n << ", must be sent outlen = " << outlen << endl; + if (n < (int)outlen) + { + cerr << "MJPG_sender: kill client " << s << endl; + //::shutdown(s, 2); + close_socket(s); + FD_CLR(s, &master); + } + } + } + if (close_all_sockets) { + int result = close_socket(sock); + cerr << "MJPG_sender: close acceptor: " << result << " \n\n"; + } + return true; + } +}; +// ---------------------------------------- + +static std::mutex mtx_mjpeg; + +//struct mat_cv : cv::Mat { int a[0]; }; + +void send_mjpeg(mat_cv* mat, int port, int timeout, int quality) +{ + try { + std::lock_guard<std::mutex> lock(mtx_mjpeg); + static MJPG_sender wri(port, timeout, quality); + //cv::Mat mat = cv::cvarrToMat(ipl); + wri.write(*(cv::Mat*)mat); + std::cout << " MJPEG-stream sent. \n"; + } + catch (...) { + cerr << " Error in send_mjpeg() function \n"; + } +} +// ---------------------------------------- + +std::string get_system_frame_time_string() +{ + std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + static std::mutex mtx; + std::lock_guard<std::mutex> lock(mtx); + struct tm *tmp_buf = localtime(&t); + char buff[256]; + std::strftime(buff, 256, "%A %F %T", tmp_buf); + std::string system_frame_time = buff; + return system_frame_time; +} +// ---------------------------------------- + + +#ifdef __CYGWIN__ +int send_http_post_request(char *http_post_host, int server_port, const char *videosource, + detection *dets, int nboxes, int classes, char **names, long long int frame_id, int ext_output, int timeout) +{ + std::cerr << " send_http_post_request() isn't implemented \n"; + return 0; +} +#else // __CYGWIN__ + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif + +#ifndef NI_NUMERICHOST +#define NI_NUMERICHOST 0x02 +#endif + +//#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "httplib.h" + +// https://webhook.site/ +// https://github.com/yhirose/cpp-httplib +// sent POST http request +int send_http_post_request(char *http_post_host, int server_port, const char *videosource, + detection *dets, int nboxes, int classes, char **names, long long int frame_id, int ext_output, int timeout) +{ + const float thresh = 0.005; // function get_network_boxes() has already filtred dets by actual threshold + + std::string message; + + for (int i = 0; i < nboxes; ++i) { + char labelstr[4096] = { 0 }; + int class_id = -1; + for (int j = 0; j < classes; ++j) { + int show = strncmp(names[j], "dont_show", 9); + if (dets[i].prob[j] > thresh && show) { + if (class_id < 0) { + strcat(labelstr, names[j]); + class_id = j; + char buff[10]; + sprintf(buff, " (%2.0f%%)", dets[i].prob[j] * 100); + strcat(labelstr, buff); + } + else { + strcat(labelstr, ", "); + strcat(labelstr, names[j]); + } + printf("%s: %.0f%% ", names[j], dets[i].prob[j] * 100); + } + } + if (class_id >= 0) { + message += std::string(names[class_id]) + std::string(", id: ") + std::to_string(class_id) + "\n"; + } + } + + if (!message.empty()) + { + std::string time = get_system_frame_time_string(); + message += "\ntime:\n" + time + "\n"; + message += "videosource:\n" + std::string(videosource); + + std::string http_post_host_str = http_post_host; + int slash_index = http_post_host_str.find("/"); + + std::string http_path = http_post_host_str.substr(slash_index, http_post_host_str.length() - slash_index); + http_post_host_str = http_post_host_str.substr(0, slash_index); + + // send HTTP-Post request + httplib::Client cli(http_post_host_str.c_str(), server_port, timeout); + auto res = cli.Post(http_path.c_str(), message, "text/plain"); + + return 1; + } + + return 0; +} +#endif // __CYGWIN__ + +#endif // OPENCV + +// ----------------------------------------------------- + +#if __cplusplus >= 201103L || _MSC_VER >= 1900 // C++11 + +#include <chrono> +#include <iostream> + +static std::chrono::steady_clock::time_point steady_start, steady_end; +static double total_time; + +double get_time_point() { + std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); + //uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(current_time.time_since_epoch()).count(); + return std::chrono::duration_cast<std::chrono::microseconds>(current_time.time_since_epoch()).count(); +} + +void start_timer() { + steady_start = std::chrono::steady_clock::now(); +} + +void stop_timer() { + steady_end = std::chrono::steady_clock::now(); +} + +double get_time() { + double took_time = std::chrono::duration<double>(steady_end - steady_start).count(); + total_time += took_time; + return took_time; +} + +void stop_timer_and_show() { + stop_timer(); + std::cout << " " << get_time() * 1000 << " msec" << std::endl; +} + +void stop_timer_and_show_name(char *name) { + stop_timer(); + std::cout << " " << name; + std::cout << " " << get_time() * 1000 << " msec" << std::endl; +} + +void show_total_time() { + std::cout << " Total: " << total_time * 1000 << " msec" << std::endl; +} + + +int custom_create_thread(custom_thread_t * tid, const custom_attr_t * attr, void *(*func) (void *), void *arg) +{ + std::thread *ptr = new std::thread(func, arg); + *tid = (custom_thread_t *)ptr; + if (tid) return 0; + else return -1; +} + +int custom_join(custom_thread_t tid, void **value_ptr) +{ + std::thread *ptr = (std::thread *)tid; + if (ptr) { + ptr->join(); + delete ptr; + return 0; + } + else printf(" Error: ptr of thread is NULL in custom_join() \n"); + + return -1; +} + +int custom_atomic_load_int(volatile int* obj) +{ + const volatile std::atomic<int>* ptr_a = (const volatile std::atomic<int>*)obj; + return std::atomic_load(ptr_a); +} + +void custom_atomic_store_int(volatile int* obj, int desr) +{ + volatile std::atomic<int>* ptr_a = (volatile std::atomic<int>*)obj; + std::atomic_store(ptr_a, desr); +} + +int get_num_threads() +{ + return std::thread::hardware_concurrency(); +} + +#if !defined(__MINGW64__) +void this_thread_sleep_for(int ms_time) +{ + std::chrono::milliseconds dura(ms_time); + std::this_thread::sleep_for(dura); +} +#else +void this_thread_sleep_for(int ms_time) +{ + std::cerr << " this_thread_sleep_for() isn't implemented \n"; + return; +} +#endif + +void this_thread_yield() +{ + std::this_thread::yield(); +} + +#else // C++11 +#include <iostream> + +double get_time_point() { return 0; } +void start_timer() {} +void stop_timer() {} +double get_time() { return 0; } +void stop_timer_and_show() { + std::cout << " stop_timer_and_show() isn't implemented " << std::endl; +} +void stop_timer_and_show_name(char *name) { stop_timer_and_show(); } +void total_time() {} +#endif // C++11 + +#include <deque> +#include <vector> +#include <iostream> +#include "blas.h" +#include "utils.h" + +struct similarity_detections_t { + int old_id, new_id; + float sim; +}; + +int check_prob(detection det, float thresh) +{ + for (int i = 0; i < det.classes; ++i) { + if (det.prob[i] > thresh) return 1; + } + return 0; +} + +int check_classes_id(detection det1, detection det2, float thresh) +{ + if (det1.classes != det2.classes) { + printf(" Error: det1.classes != det2.classes \n"); + getchar(); + } + + int det1_id = -1; + float det1_prob = 0; + int det2_id = -1; + float det2_prob = 0; + + for (int i = 0; i < det1.classes; ++i) { + if (det1.prob[i] > thresh && det1.prob[i] > det1_prob) { + det1_prob = det1.prob[i]; + det1_id = i; + } + if (det2.prob[i] > thresh && det2.prob[i] > det2_prob) { + det2_prob = det2.prob[i]; + det2_id = i; + } + } + + if (det1_id == det2_id && det2_id != -1) return 1; + + //for (int i = 0; i < det1.classes; ++i) { + // if (det1.prob[i] > thresh && det2.prob[i] > thresh) return 1; + //} + return 0; +} + +int fill_remaining_id(detection *new_dets, int new_dets_num, int new_track_id, float thresh, int detection_count) +{ + for (int i = 0; i < new_dets_num; ++i) { + if (new_dets[i].track_id == 0 && check_prob(new_dets[i], thresh)) { + //printf(" old_tid = %d, new_tid = %d, sim = %f \n", new_dets[i].track_id, new_track_id, new_dets[i].sim); + if (new_dets[i].sort_class > detection_count) { + new_dets[i].track_id = new_track_id; + new_track_id++; + } + } + } + return new_track_id; +} + +float *make_float_array(float* src, size_t size) +{ + float *dst = (float*)xcalloc(size, sizeof(float)); + memcpy(dst, src, size*sizeof(float)); + return dst; +} + +struct detection_t : detection { + int det_count; + detection_t(detection det) : detection(det), det_count(0) + { + if (embeddings) embeddings = make_float_array(det.embeddings, embedding_size); + if (prob) prob = make_float_array(det.prob, classes); + if (uc) uc = make_float_array(det.uc, 4); + } + + detection_t(detection_t const& det) : detection(det) + { + if (embeddings) embeddings = make_float_array(det.embeddings, embedding_size); + if (prob) prob = make_float_array(det.prob, classes); + if (uc) uc = make_float_array(det.uc, 4); + } + + ~detection_t() { + if (embeddings) free(embeddings); + if (prob) free(prob); + if (uc) free(uc); + } +}; + + + +void set_track_id(detection *new_dets, int new_dets_num, float thresh, float sim_thresh, float track_ciou_norm, int deque_size, int dets_for_track, int dets_for_show) +{ + static int new_track_id = 1; + static std::deque<std::vector<detection_t>> old_dets_dq; + + // copy detections from queue of vectors to the one vector + std::vector<detection_t> old_dets; + for (std::vector<detection_t> &v : old_dets_dq) { + for (int i = 0; i < v.size(); ++i) { + old_dets.push_back(v[i]); + } + } + + std::vector<similarity_detections_t> sim_det(old_dets.size() * new_dets_num); + + // calculate similarity + for (int old_id = 0; old_id < old_dets.size(); ++old_id) { + for (int new_id = 0; new_id < new_dets_num; ++new_id) { + const int index = old_id*new_dets_num + new_id; + const float sim = cosine_similarity(new_dets[new_id].embeddings, old_dets[old_id].embeddings, old_dets[0].embedding_size); + sim_det[index].new_id = new_id; + sim_det[index].old_id = old_id; + sim_det[index].sim = sim; + } + } + + // sort similarity + std::sort(sim_det.begin(), sim_det.end(), [](similarity_detections_t v1, similarity_detections_t v2) { return v1.sim > v2.sim; }); + //if(sim_det.size() > 0) printf(" sim_det_first = %f, sim_det_end = %f \n", sim_det.begin()->sim, sim_det.rbegin()->sim); + + std::vector<int> new_idx(new_dets_num, 1); + std::vector<int> old_idx(old_dets.size(), 1); + std::vector<int> track_idx(new_track_id, 1); + + // match objects + for (int index = 0; index < new_dets_num*old_dets.size(); ++index) { + const int new_id = sim_det[index].new_id; + const int old_id = sim_det[index].old_id; + const int track_id = old_dets[old_id].track_id; + const int det_count = old_dets[old_id].sort_class; + //printf(" ciou = %f \n", box_ciou(new_dets[new_id].bbox, old_dets[old_id].bbox)); + if (track_idx[track_id] && new_idx[new_id] && old_idx[old_id] && check_classes_id(new_dets[new_id], old_dets[old_id], thresh)) { + float sim = sim_det[index].sim; + //float ciou = box_ciou(new_dets[new_id].bbox, old_dets[old_id].bbox); + float ciou = box_iou(new_dets[new_id].bbox, old_dets[old_id].bbox); + sim = sim * (1 - track_ciou_norm) + ciou * track_ciou_norm; + if (sim_thresh < sim && new_dets[new_id].sim < sim) { + new_dets[new_id].sim = sim; + new_dets[new_id].track_id = track_id; + new_dets[new_id].sort_class = det_count + 1; + //new_idx[new_id] = 0; + old_idx[old_id] = 0; + if(track_id) track_idx[track_id] = 0; + } + } + } + + // set new track_id + new_track_id = fill_remaining_id(new_dets, new_dets_num, new_track_id, thresh, dets_for_track); + + // store new_detections to the queue of vectors + std::vector<detection_t> new_det_vec; + for (int i = 0; i < new_dets_num; ++i) { + if (check_prob(new_dets[i], thresh)) { + new_det_vec.push_back(new_dets[i]); + } + } + + // add new + old_dets_dq.push_back(new_det_vec); + // remove old + if (old_dets_dq.size() > deque_size) old_dets_dq.pop_front(); + + // remove detection which were detected only on few frames + for (int i = 0; i < new_dets_num; ++i) { + if (new_dets[i].sort_class < dets_for_show) { + for (int j = 0; j < new_dets[i].classes; ++j) { + new_dets[i].prob[j] = 0; + } + } + } +} -- Gitblit v1.8.0