From 82f630cac763a168b67d3d51e60eb860e2aa26de Mon Sep 17 00:00:00 2001 From: AlexeyAB Date: Sun, 18 Feb 2018 02:31:56 +0300 Subject: [PATCH] Added param -http_port 8090 to show MJPEG-stream in the WebBrowser (Chrome/Firefox) --- Makefile | 7 +- build/darknet/darknet.vcxproj | 2 + .../darknet/x64/darknet_demo_mjpeg_stream.cmd | 7 + build/darknet/yolo_cpp_dll.vcxproj | 2 + mjpeg_stream.sh | 8 + src/coco.c | 4 +- src/demo.c | 8 +- src/demo.h | 2 +- src/detector.c | 3 +- src/http_stream.cpp | 197 ++++++++++++++++++ src/http_stream.h | 15 ++ src/image.c | 17 +- src/yolo.c | 4 +- 13 files changed, 265 insertions(+), 11 deletions(-) create mode 100644 build/darknet/x64/darknet_demo_mjpeg_stream.cmd create mode 100644 mjpeg_stream.sh create mode 100644 src/http_stream.cpp create mode 100644 src/http_stream.h diff --git a/Makefile b/Makefile index 2952ccf8..f8bd4a53 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ CFLAGS+= -DCUDNN -I/usr/local/cudnn/include LDFLAGS+= -L/usr/local/cudnn/lib64 -lcudnn endif -OBJ=gemm.o utils.o cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o super.o voxel.o tree.o +OBJ=http_stream.o gemm.o utils.o cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o super.o voxel.o tree.o ifeq ($(GPU), 1) LDFLAGS+= -lstdc++ OBJ+=convolutional_kernels.o activation_kernels.o im2col_kernels.o col2im_kernels.o blas_kernels.o crop_layer_kernels.o dropout_layer_kernels.o maxpool_layer_kernels.o network_kernels.o avgpool_layer_kernels.o @@ -91,11 +91,14 @@ $(APPNAMESO): $(LIBNAMESO) src/yolo_v2_class.hpp src/yolo_console_dll.cpp endif $(EXEC): $(OBJS) - $(CC) $(COMMON) $(CFLAGS) $^ -o $@ $(LDFLAGS) + $(CPP) $(COMMON) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(OBJDIR)%.o: %.c $(DEPS) $(CC) $(COMMON) $(CFLAGS) -c $< -o $@ +$(OBJDIR)%.o: %.cpp $(DEPS) + $(CPP) $(COMMON) $(CFLAGS) -c $< -o $@ + $(OBJDIR)%.o: %.cu $(DEPS) $(NVCC) $(ARCH) $(COMMON) --compiler-options "$(CFLAGS)" -c $< -o $@ diff --git a/build/darknet/darknet.vcxproj b/build/darknet/darknet.vcxproj index b4da086a..0eba8f8a 100644 --- a/build/darknet/darknet.vcxproj +++ b/build/darknet/darknet.vcxproj @@ -201,6 +201,7 @@ + @@ -254,6 +255,7 @@ + diff --git a/build/darknet/x64/darknet_demo_mjpeg_stream.cmd b/build/darknet/x64/darknet_demo_mjpeg_stream.cmd new file mode 100644 index 00000000..044b5b78 --- /dev/null +++ b/build/darknet/x64/darknet_demo_mjpeg_stream.cmd @@ -0,0 +1,7 @@ +rem Run this file and then open URL in Chrome/Firefox: rem http://localhost:8090 +rem Or open: http://ip-address:8090 + +darknet.exe detector demo data/voc.data yolo-voc.cfg yolo-voc.weights test.mp4 -i 0 -http_port 8090 + + +pause \ No newline at end of file diff --git a/build/darknet/yolo_cpp_dll.vcxproj b/build/darknet/yolo_cpp_dll.vcxproj index ee45c38c..b4a97a30 100644 --- a/build/darknet/yolo_cpp_dll.vcxproj +++ b/build/darknet/yolo_cpp_dll.vcxproj @@ -203,6 +203,7 @@ + @@ -258,6 +259,7 @@ + diff --git a/mjpeg_stream.sh b/mjpeg_stream.sh new file mode 100644 index 00000000..e3e9746a --- /dev/null +++ b/mjpeg_stream.sh @@ -0,0 +1,8 @@ +rem Run this file and then open URL in Chrome/Firefox: rem http://localhost:8090 +rem Or open: http://ip-address:8090 + +./darknet detector demo ./cfg/voc.data ./cfg/yolo-voc.cfg ./yolo-voc.weights test50.mp4 -i 0 -thresh 0.2 -http_port 8090 + + + +pause \ No newline at end of file diff --git a/src/coco.c b/src/coco.c index 8bf5ee74..b0e03882 100644 --- a/src/coco.c +++ b/src/coco.c @@ -367,6 +367,7 @@ void test_coco(char *cfgfile, char *weightfile, char *filename, float thresh) void run_coco(int argc, char **argv) { + int http_stream_port = find_int_arg(argc, argv, "-http_port", -1); char *out_filename = find_char_arg(argc, argv, "-out_filename", 0); char *prefix = find_char_arg(argc, argv, "-prefix", 0); float thresh = find_float_arg(argc, argv, "-thresh", .2); @@ -385,5 +386,6 @@ void run_coco(int argc, char **argv) else if(0==strcmp(argv[2], "train")) train_coco(cfg, weights); else if(0==strcmp(argv[2], "valid")) validate_coco(cfg, weights); else if(0==strcmp(argv[2], "recall")) validate_coco_recall(cfg, weights); - else if(0==strcmp(argv[2], "demo")) demo(cfg, weights, thresh, cam_index, filename, coco_classes, 80, frame_skip, prefix, out_filename); + else if(0==strcmp(argv[2], "demo")) demo(cfg, weights, thresh, cam_index, filename, coco_classes, 80, frame_skip, + prefix, out_filename, http_stream_port); } diff --git a/src/demo.c b/src/demo.c index eece00a8..9c3fea49 100644 --- a/src/demo.c +++ b/src/demo.c @@ -49,6 +49,7 @@ static IplImage* ipl_images[FRAMES]; static float *avg; void draw_detections_cv(IplImage* show_img, int num, float thresh, box *boxes, float **probs, char **names, image **alphabet, int classes); +void show_image_cv_ipl(IplImage *disp, const char *name, const char *out_filename, int http_stream_port); image get_image_from_stream_resize(CvCapture *cap, int w, int h, IplImage** in_img); IplImage* in_img; IplImage* det_img; @@ -115,7 +116,8 @@ double get_wall_time() return (double)time.tv_sec + (double)time.tv_usec * .000001; } -void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const char *filename, char **names, int classes, int frame_skip, char *prefix, char *out_filename) +void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const char *filename, char **names, int classes, + int frame_skip, char *prefix, char *out_filename, int http_stream_port) { //skip = frame_skip; image **alphabet = load_alphabet(); @@ -194,7 +196,7 @@ void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const ch if(!prefix){ //show_image(disp, "Demo"); - show_image_cv_ipl(show_img, "Demo", out_filename); + show_image_cv_ipl(show_img, "Demo", out_filename, http_stream_port); int c = cvWaitKey(1); if (c == 10){ if(frame_skip == 0) frame_skip = 60; @@ -244,7 +246,7 @@ void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const ch } } #else -void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const char *filename, char **names, int classes, int frame_skip, char *prefix, char *out_filename) +void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const char *filename, char **names, int classes, int frame_skip, char *prefix, char *out_filename, int http_stream_port) { fprintf(stderr, "Demo needs OpenCV for webcam images.\n"); } diff --git a/src/demo.h b/src/demo.h index 36fa9559..537066f2 100644 --- a/src/demo.h +++ b/src/demo.h @@ -2,6 +2,6 @@ #define DEMO #include "image.h" -void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const char *filename, char **names, int classes, int frame_skip, char *prefix, char *out_filename); +void demo(char *cfgfile, char *weightfile, float thresh, int cam_index, const char *filename, char **names, int classes, int frame_skip, char *prefix, char *out_filename, int http_stream_port); #endif diff --git a/src/detector.c b/src/detector.c index d860a6ad..c851b382 100644 --- a/src/detector.c +++ b/src/detector.c @@ -859,6 +859,7 @@ void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filenam void run_detector(int argc, char **argv) { + int http_stream_port = find_int_arg(argc, argv, "-http_port", -1); char *out_filename = find_char_arg(argc, argv, "-out_filename", 0); char *prefix = find_char_arg(argc, argv, "-prefix", 0); float thresh = find_float_arg(argc, argv, "-thresh", .24); @@ -911,6 +912,6 @@ void run_detector(int argc, char **argv) char **names = get_labels(name_list); if(filename) if (filename[strlen(filename) - 1] == 0x0d) filename[strlen(filename) - 1] = 0; - demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, out_filename); + demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, out_filename, http_stream_port); } } diff --git a/src/http_stream.cpp b/src/http_stream.cpp new file mode 100644 index 00000000..4392a278 --- /dev/null +++ b/src/http_stream.cpp @@ -0,0 +1,197 @@ +#ifdef OPENCV +// +// 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) +// + +// +// socket related abstractions: +// +#ifdef _WIN32 +#pragma comment(lib, "ws2_32.lib") +#include +#include +#include +#define PORT unsigned long +#define ADDRPOINTER int* +struct _INIT_W32DATA +{ + WSADATA w; + _INIT_W32DATA() { WSAStartup(MAKEWORD(2, 1), &w); } +} _init_once; +#else /* ! win32 */ +#include +#include +#include +#include +#include +#include +#include +#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 +#endif /* _WIN32 */ + +#include +#include +using std::cerr; +using std::endl; + +#include "opencv2/opencv.hpp" +using namespace cv; + +#include "http_stream.h" + + +class MJPGWriter +{ + SOCKET sock; + SOCKET maxfd; + fd_set master; + int timeout; // master sock timeout, shutdown after timeout millis. + int quality; // jpeg compression [1..100] + + int _write(int sock, char *s, int len) + { + if (len < 1) { len = strlen(s); } + return ::send(sock, s, len, 0); + } + +public: + + MJPGWriter(int port = 0, int _timeout = 200000, int _quality = 30) + : sock(INVALID_SOCKET) + , timeout(_timeout) + , quality(_quality) + { + FD_ZERO(&master); + if (port) + open(port); + } + + ~MJPGWriter() + { + release(); + } + + bool release() + { + if (sock != INVALID_SOCKET) + ::shutdown(sock, 2); + sock = (INVALID_SOCKET); + return false; + } + + 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); + if (::bind(sock, (SOCKADDR*)&address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) + { + cerr << "error : couldn't bind sock " << sock << " to port " << port << "!" << endl; + return release(); + } + if (::listen(sock, 10) == SOCKET_ERROR) + { + cerr << "error : 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 to = { 0,timeout }; + if (::select(maxfd+1, &rread, NULL, NULL, &to) <= 0) + return true; // nothing broken, there's just noone listening + + std::vector outbuf; + std::vector params; + params.push_back(IMWRITE_JPEG_QUALITY); + params.push_back(quality); + cv::imencode(".jpg", frame, outbuf, params); + unsigned int outlen = outbuf.size(); + +#ifdef _WIN32 + for (unsigned i = 0; iclient ? 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 << "new client " << client << endl; + } + else // existing client, just stream pix + { + char head[400]; + sprintf(head, "--mjpegstream\r\nContent-Type: image/jpeg\r\nContent-Length: %lu\r\n\r\n", outlen); + _write(s, head, 0); + int n = _write(s, (char*)(&outbuf[0]), outlen); + //cerr << "known client " << s << " " << n << endl; + if (n < outlen) + { + cerr << "kill client " << s << endl; + ::shutdown(s, 2); + FD_CLR(s, &master); + } + } + } + return true; + } +}; + + + +void send_mjpeg(IplImage* ipl, int port, int timeout, int quality) { + static MJPGWriter wri(port, timeout, quality); + cv::Mat mat = cv::cvarrToMat(ipl); + wri.write(mat); + std::cout << " MJPEG-stream sent. \n"; +} + +#endif // OPENCV \ No newline at end of file diff --git a/src/http_stream.h b/src/http_stream.h new file mode 100644 index 00000000..c9b05b68 --- /dev/null +++ b/src/http_stream.h @@ -0,0 +1,15 @@ +#pragma once +#ifndef HTTP_STREAM_H +#define HTTP_STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +void send_mjpeg(IplImage* ipl, int port, int timeout, int quality); + +#ifdef __cplusplus +} +#endif + +#endif // HTTP_STREAM_H \ No newline at end of file diff --git a/src/image.c b/src/image.c index 3afe0271..efa7185c 100644 --- a/src/image.c +++ b/src/image.c @@ -14,13 +14,14 @@ #include "opencv2/highgui/highgui_c.h" #include "opencv2/imgproc/imgproc_c.h" #include "opencv2/core/version.hpp" +#include "http_stream.h" #ifndef CV_VERSION_EPOCH #include "opencv2/videoio/videoio_c.h" #include "opencv2/imgcodecs/imgcodecs_c.h" +#include "http_stream.h" #endif #endif - int windows = 0; float colors[6][3] = { {1,0,1}, {0,0,1},{0,1,1},{0,1,0},{1,1,0},{1,0,0} }; @@ -527,7 +528,7 @@ void show_image_cv(image p, const char *name) } -void show_image_cv_ipl(IplImage *disp, const char *name, const char *out_filename) +void show_image_cv_ipl(IplImage *disp, const char *name, const char *out_filename, int http_stream_port) { if (disp == NULL) return; char buff[256]; @@ -538,6 +539,18 @@ void show_image_cv_ipl(IplImage *disp, const char *name, const char *out_filenam ++windows; cvShowImage(buff, disp); + + // http mjpeg stream: http://localhost:8090 + // use URL with the port number stated in your command line instead of 8090 + if (http_stream_port > 0) { + //int port = 8090; + int port = http_stream_port; + int timeout = 200; + int jpeg_quality = 30; // 1 - 100 + send_mjpeg(disp, port, timeout, jpeg_quality); + } + + if(out_filename) { CvSize size; diff --git a/src/yolo.c b/src/yolo.c index e8b9e8bd..2ee0d6d2 100644 --- a/src/yolo.c +++ b/src/yolo.c @@ -340,6 +340,7 @@ void test_yolo(char *cfgfile, char *weightfile, char *filename, float thresh) void run_yolo(int argc, char **argv) { + int http_stream_port = find_int_arg(argc, argv, "-http_port", -1); char *out_filename = find_char_arg(argc, argv, "-out_filename", 0); char *prefix = find_char_arg(argc, argv, "-prefix", 0); float thresh = find_float_arg(argc, argv, "-thresh", .2); @@ -357,5 +358,6 @@ void run_yolo(int argc, char **argv) else if(0==strcmp(argv[2], "train")) train_yolo(cfg, weights); else if(0==strcmp(argv[2], "valid")) validate_yolo(cfg, weights); else if(0==strcmp(argv[2], "recall")) validate_yolo_recall(cfg, weights); - else if(0==strcmp(argv[2], "demo")) demo(cfg, weights, thresh, cam_index, filename, voc_names, 20, frame_skip, prefix, out_filename); + else if(0==strcmp(argv[2], "demo")) demo(cfg, weights, thresh, cam_index, filename, voc_names, 20, frame_skip, + prefix, out_filename, http_stream_port); }