[实训题目EmoProfo]服务端上传视频处理(二)

服务端上传视频处理(二)

背景

在实现好服务端的Python图像分析服务后,我们之前提到的遗留问题应该被解决:即dlib精确度不够的问题,下面我们将通过darknet完成人脸的检测。也就是说,在之前完成的工作的基础之上,将darknet和我们的服务进行集成。

darknet进一步修饰

之前我们实现的darknet中不能很好地处理一张图片内有多个目标的情况,接下来我们需要完成对darknet的Python接口的处理,使其能够一张图片处理中返回多个结果

处理流程改变

过去,我们将所有的识别信息全部存储在darknet本地的数组中,现在看来这样是不可取的,原因有以下两点:

  • 我们无法在darknet模块初始化阶段确定图像的数量,更不能确定结果的数量
  • 在提取结果的过程中,我们很难分辨那个结果属于那张图片

基于上述问题,我们重新设计了darknet的结果存储机制,使其更加灵活:

  • 采用和图片输入一样的链状缓冲区,存储结果,每一个节点中存储一张图片的结果数量,结果数据
  • darknet模块的使用流程变成:
    • 输入一批图片
    • 多次提取结果,每次可以取出一张图片的,顺序符合输入顺序

具体改动和调试

  • 链表的实现和线程安全性

    过去的图像缓冲区中存在问题,就是没考虑到线程安全性,虽然在当前的使用方式下,发生线程安全问题的可能性不高,但是为了保证正确性,我们还是重新加入了相关措施,并且屏蔽了外界过程直接便利链表的功能,直接向外界提供了函数结构来完成链状队列的存取,具体内容如下

    • pydarknet.h
    #include "darknet.h"
    #include <numpy/ndarrayobject.h>
    #include <string.h>
    #include <stdio.h>
    
    #ifndef __PYDARKNET_
    #define __PYDARKNET_
    
    #define NET_SIZE 416
    #define IN_W 640
    #define IN_H 480
    #define NEW_H 312
    #define HALF_H 180	//6线程用
    #define NEW_W NET_SIZE
    #define SCALE ((float)(IN_W - 1) / (NET_SIZE -1))
    
    /*
     * 正经的从ndarray.data里取值的方法
     * ptr	: double*
     * w	: int
     * h	: int
     * c	: int
     * 类型错了谁也救不了
     */
    //#define GET_PIXEL(ptr, w, h, c) ((float) (ptr)[(h)*IN_W*3 + (w)*3 + 2 - (c)]/255.)
    
    
    network * net;
    char** names;
    int batch_size;
    
    int** do_detection(char*, float);
    void __finalize(void);
    void __init(void);
    
    #ifdef radical__
    int total_size;	//总共的图片数量
    int* g_res_store;		//在识别结束之前所有的结果暂时先放在内存里
    
    typedef struct __linked_buffer {
    	struct __linked_buffer*	next;			//下一个batch
    	image*					batch_pic;		//本batch的转换结果(图片统一free)
    	float*					batch_pic_data; //为了方便资源回收&引用
    	float					thresh;			//每个batch的thresh,虽然不认为会变化
    	int						batch_size;		//下一个batch的大小
    }linked_buffer_t;
    
    /**
     * @brief list_head
     * 链表头指针,我不打算用什么空头
     */
    linked_buffer_t* buffer_head;
    void buffer_init();
    void buffer_finalize();
    linked_buffer_t* create_buffer(int buffer_size, float thresh);
    void free_buffer(linked_buffer_t* rubbish);
    #endif
    
    #ifdef app__
    typedef struct __linked_buffer {
    	struct __linked_buffer*	next;			//下一个batch
    	image*					batch_pic;		//本batch的转换结果(图片统一free)
    	float*					batch_pic_data; //为了方便资源回收&引用
    	float					thresh;			//每个batch的thresh,虽然不认为会变化
    	int						batch_size;		//下一个batch的大小
    }linked_buffer_t;
    
    /**
     * @brief list_head
     * 链表头指针,我不打算用什么空头
     */
    void buffer_init();
    void buffer_finalize();
    linked_buffer_t* create_buffer(int buffer_size, float thresh);
    linked_buffer_t* retrieve_buffer();
    void free_buffer(linked_buffer_t* rubbish);
    
    typedef struct __linked_res {
    	struct __linked_res*		next;	//下一张图片的指针
    	int						dets;	//当前图片检测出的人脸数量
    	int*					data;	//存储的数据,长度是dets×4
    }linked_res_t;
    
    void res_init();									//初始化信号量
    void res_finalize();								//销毁信号量
    linked_res_t* create_res(int dets, int* data);	//创建一个新的节点,加入列表
    linked_res_t* retrieve_res();								//节点中的数据已经送出,释放节点的空间,但是不释放数组
    void destroy_res(linked_res_t* rubbish);			//由于某种原因当前节点作废,释放掉节点和内部数据域的空间
    
    #endif
    
    #endif
    
    • linked_buffer.c
    #include "pydarknet.h"
    #include <semaphore.h>
    
    #define MAX_BUFFER_SIZE 3
    static sem_t size_sig;
    static sem_t res_sig;
    static pthread_mutex_t buffer_mutex;
    static pthread_mutex_t res_mutex;
    
    linked_buffer_t* buffer_head;
    linked_buffer_t* buffer_rear;
    
    /**
     * @brief create_buffer
     * 创建一个缓冲区,顺便把空间也给他申请了吧
     * @param buffer_size 当前的batch_size
     * @param buffered_data 需要转换的图片,在这里就申请好空间,并在这里释放
     * @return
     */
    linked_buffer_t* create_buffer(int buffer_size, float thresh)
    {
    	sem_wait(&size_sig);
    	//为image结构申请空间
    	image* sized_pics = calloc(buffer_size, sizeof(image));
    	float* cont_data_field = calloc(batch_size*NET_SIZE*NET_SIZE*3, sizeof(float));
    	for(int i = 0; i < buffer_size; i++)
    	{
    		sized_pics[i].data = cont_data_field + i*3*NET_SIZE*NET_SIZE;
    		sized_pics[i].w = NET_SIZE;
    		sized_pics[i].h = NET_SIZE;
    		sized_pics[i].c = 3;
    	}
    	//申请新节点并赋值
    	linked_buffer_t* ret = malloc(sizeof(linked_buffer_t));
    
    	ret->next = NULL;
    	ret->batch_size = buffer_size;
    	ret->thresh = thresh;
    	ret->batch_pic = sized_pics;
    	ret->batch_pic_data = cont_data_field;
    
    	pthread_mutex_lock(&buffer_mutex);		//sync
    	if(buffer_head == NULL && buffer_rear == NULL) buffer_head = buffer_rear = ret;
    	else
    	{
    		buffer_head->next = ret;
    		buffer_head = ret;
    	}
    	pthread_mutex_unlock(&buffer_mutex);		//sync fin
    	printf("allocated new buffer on %x\n", ret);
    	return ret;
    }
    
    linked_buffer_t* retrieve_buffer()
    {
    	pthread_mutex_lock(&buffer_mutex); //sync
    	linked_buffer_t* retval = buffer_rear;
    	if(retval == NULL)
    	{
    		pthread_mutex_unlock(&buffer_mutex);
    		return NULL;
    	}
    	buffer_rear = buffer_rear->next;
    	if(buffer_rear == NULL)
    		buffer_head = NULL;
    	sem_post(&size_sig);
    	pthread_mutex_unlock(&buffer_mutex);		//sync fin
    	return retval;
    }
    
    /**
     * @brief free_buffer
     * 按照上面释放掉申请的所有资源
     * @param rubbish
     */
    void free_buffer(linked_buffer_t* rubbish)
    {
    	free(rubbish->batch_pic_data);
    	free(rubbish->batch_pic);
    	free(rubbish);
    }
    
    void buffer_init()
    {
    	buffer_head = buffer_rear = NULL;
    	sem_init(&size_sig, 0, MAX_BUFFER_SIZE);
    	pthread_mutex_init(&buffer_mutex, NULL);
    }
    
    void buffer_finalize()
    {
    	pthread_mutex_lock(&buffer_mutex);
    	buffer_head = buffer_rear = NULL;
    	sem_destroy(&size_sig);
    	pthread_mutex_unlock(&buffer_mutex);
    	pthread_mutex_destroy(&buffer_mutex);
    }
    
    
    linked_res_t* res_head;
    linked_res_t* res_rear;
    
    void res_init()
    {
    	res_head = res_rear = NULL;
    	sem_init(&res_sig, 0, 0);
    	pthread_mutex_init(&res_mutex, NULL);
    }
    
    void res_finalize()
    {
    	pthread_mutex_lock(&res_mutex);
    	res_head = res_rear = NULL;
    	sem_destroy(&res_sig);
    	pthread_mutex_unlock(&res_mutex);
    	pthread_mutex_destroy(&res_mutex);
    }
    
    linked_res_t* create_res(int size, int* res)
    {
    	linked_res_t* n = malloc(sizeof(linked_res_t));
    	n->data = res;
    	n->dets = size;
    	n->next = NULL;
    
    	pthread_mutex_lock(&res_mutex);
    	if(res_head == NULL && res_rear == NULL) res_head = res_rear = n;
    	else{
    		res_head->next = n;
    		res_head = n;
    	}
    	sem_post(&res_sig);
    	pthread_mutex_unlock(&res_mutex);
    	return n;
    }
    
    linked_res_t* retrieve_res()
    {
    	sem_wait(&res_sig);
    	pthread_mutex_lock(&res_mutex);
    	linked_res_t* retval = res_rear;
    	assert(retval != NULL);
    	res_rear = res_rear->next;
    	if(res_rear == NULL)
    		res_head = NULL;
    	pthread_mutex_unlock(&res_mutex);
    	return retval;
    }
    
    /**
     * @brief free
     * @return res节点释放是否成功
     */
    void destroy_res(linked_res_t *rubbish)
    {
    	free(rubbish->data);
    	free(rubbish);
    }
    

    现在的两种缓冲区,在实现上存在如下区别:

    • 信号量的作用:图像缓冲区的信号量是用来控制缓冲区大小的,防止连续输入导致内存溢出;而结果缓冲区的信号量就是单纯为了实现同步的读者写者功能
    • 作废节点的回收:图像缓冲的节点在图像转换完成之后,就完全失去了作用,需要释放包括其指针成员指向的空间在内的所有空间;而结果缓冲区中,数据缓冲区是要作为Python新对象的数据域的,不能释放,节点回收只需使用free释放掉节点本身的空间就可以了
  • 识别结果处理的重新定义

    darknet框架本身是支持多个结果的,所以我们只需要讲返回值调整成多个结果即可,但是问题是如何返回。因为调用函数的时候并不知道返回的结果有多少个

    //这个玩意是我自己加的,让我们把识别结果转化成int[]就结束
    int list_detection_one_class(image im, detection *dets, int num, float thresh, int** ptr_buffer)
    {
        int i, cnt = 0;
        
        for(i = 0; i < num; i++)
        {
            if(dets[i].prob[0] >= thresh) cnt++;
        }
        if(cnt == 0) return 0;
        
        printf("cnt = %d\n",cnt);
        *ptr_buffer = calloc(sizeof(int), cnt*4);
        
        for(cnt = 0,i = 0; i < num; ++i){
            
            if(dets[i].prob[0] < thresh) continue;
            
        	box b = dets[i].bbox;
            //printf("%f %f %f %f\n", b.x, b.y, b.w, b.h);
    
            /* //可选方案
            int w__ = im.w*b.w;
            int h__ = im.h*b.h;
            int avg = (w__+h__) /2;
            int left  = b.x*im.w-avg/2;
            int right = b.x*im.w+avg/2;
            int top   = b.y*im.h-avg/2;
            int bot   = b.y*im.h+avg/2;
            */
            
            int left  = (b.x-b.w/2.)*im.w;
            int right = (b.x+b.w/2.)*im.w;
            int top   = (b.y-b.h/2.)*im.h;
            int bot   = (b.y+b.h/2.)*im.h;
            
    
    
            if(left < 0) left = 0;
            if(right > im.w-1) right = im.w-1;
            if(top < 0) top = 0;
            if(bot > im.h-1) bot = im.h-1;
            
            (*ptr_buffer)[cnt*4] = left;
            (*ptr_buffer)[cnt*4+1] = right;
            (*ptr_buffer)[cnt*4+2] = top;
            (*ptr_buffer)[cnt*4+3] = bot;
            cnt++;
        }
        
        return cnt;
    }
    

    我们采用了返回值+输出参数的方法。代码中可选方案代表画出框的形状调整

  • 外部逻辑和接口的相关适配

    在上述改动的基础上,其他的代码也作出了相应的改动,具体如下:

    • 主逻辑变动

      1. 我们不能再通过已经处理的图片数量判断是否结束,而是在显示调用终止函数的时候,才能判断结束

        int** do_detection(char* data, float thresh)
        {
        	linked_buffer_t* new_batch = create_buffer(batch_size, thresh);//可能会阻塞!!
        	if(detect_num == -1)
        	{//还没开始工作,current_job域还是空的,由于这个分支只会执行一次
        		//det_worker.current_job = retrieve_buffer();
        		sem_post(&det_sig);
        		detect_num = 0;
        	}
        	detect_num += batch_size;
        	feed_pipeline(data, new_batch);
        	//-----------------------------
        	//printf("353:stuck here?\n");
        	//-----------------------------
        	sem_wait(&conv_idle);	//策略改变——进来的时候断言转换线程空闲,等到转换完成后退出
        	//-----------------------------
        	printf("pic %d wait success!...\n", detect_num);
        	//-----------------------------
        	return NULL;
        }
        //......
        void __finalize( void )
        {
        	int i;
        	for(i = 0; i < CONV_THS; i++)
        	{
        		pic_workers[i].src = -1;
        		sem_post(&pic_workers[i].next_batch);
        	}
        
        	create_buffer(1,0);	//通知识别线程可以结束
        	sem_wait(&det_sig);	//等待识别线程结束
        	pthread_join(&det_worker, NULL);
        
        	//printf("threads finalized\n");
        	for(i = 0; i < CONV_THS; i++)
        		sem_destroy(&pic_workers[i].next_batch);
        	//printf("sems finalized\n");
        	sem_destroy(&conv_idle);
        	sem_destroy(&det_sig);
        	buffer_finalize();
        }
        
      2. 识别线程的处理方法,现在每处理一个张图片,都要对结果进行回收

        static void* thread_detect(void* args)
        {
        	det_t*	task = (det_t*) args;
        	linked_buffer_t* job;
        	float	thresh;
        	image*	batch_pics;
        	int		b_size;
        	int*	det_res_ptr;	//检测结果的指针
        
        	sem_wait(&det_sig);	//等主线程准备好数据(串好链表)
        	while(1)
        	{
        		int batch_index;
        		while((job = retrieve_buffer()) == NULL) pthread_yield();//task->current_job;
        		thresh = job->thresh;
        		if(thresh == 0)
        		{
        			sem_post(&det_sig);
        			return NULL;
        		}
        		batch_pics = job->batch_pic;
        		b_size = job->batch_size;
        		for(batch_index = 0; batch_index < b_size; batch_index++)
        		{
        			sem_wait(&task->pic_avail);
        			//--------------------------
        			printf("det_worker : Got one pic to detect...\n");
        			//--------------------------
        			int size = test_detector(batch_pics + batch_index, thresh, &det_res_ptr);
        			create_res(size, det_res_ptr);
        			//--------------------------
        			printf("det_worker : res created at %d...\n", what_time_is_it_now() - gtimer);
        			//--------------------------
        		}
        		free_buffer(job);
        	}
        	return NULL;
        }
        

技术验证

既然是Python模块,就要使用Python代码来进行验证,正好使用opencv的webcam功能完成一个实时的测试

#!/usr/bin/python

import cv2
import pydarknet as pd
import numpy as np
import time

DataFile = "models/face.data"
CfgFile = "models/yolov3-tiny.cfg"
WeightFile = "models/yolov3-tiny.backup6"
imageSize = (360, 640, 3)

if __name__ == '__main__':
	cap = cv2.VideoCapture(0)
	pd.init_detector(DataFile, CfgFile, WeightFile)
	buf = np.zeros((1, 480, 640, 3))
	while 1:
		ret, frame = cap.read()
		buf[0] = frame
		print 'till now, nothing happens'
		pd.feed_detector(0.5, buf)
		print 'i fed it a shit!'
		res = pd.pull_result()
		#time.sleep(3)
		for i in range(res.shape[0]):
			cv2.rectangle(frame, (res[i][0],res[i][2]), (res[i][1],res[i][3]), (0,255,255), 2)
		cv2.imshow("window",frame)
		if cv2.waitKey(30) == 27:
			break
	
	cap.release()

小结

至此,我的主要工作基本完成,下面就需要专注于整个系统的集成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值