30 votes

Problèmes de classification d'images en temps réel Python avec les réseaux de neurones

Je suis d'essayer d'utiliser caffe et python pour faire des images en temps réel de la classification. Je suis à l'aide d'OpenCV en continu à partir de ma webcam dans un processus, et dans un processus séparé, à l'aide de caffe pour effectuer la classification des images sur les images extraites de la webcam. Alors je suis de passage le résultat de la classification au thread principal de sous-titrer le flux de la webcam.

Le problème est que même si j'ai une NVIDIA GPU et je suis d'effectuer le caffe des prédictions sur le GPU, le thread principal obtient ralenti. Normalement, sans faire de prédictions, ma webcam stream tourne à 30 fps; cependant, avec les prédictions, ma webcam stream obtient, au mieux, de 15 fps.

J'ai vérifié que caffe est, en effet, l'utilisation du GPU lors de la réalisation de ces prédictions, et que ma carte graphique ou GPU, la mémoire n'est pas plafonnait. J'ai aussi vérifié que mon CPU cores ne sont pas arriver au maximum à tout moment au cours du programme. Je me demande si je fais quelque chose de mal ou si il n'y a aucun moyen de garder ces 2 processus véritablement distincte. Tout conseil est le bienvenue. Voici mon code pour la référence

class Consumer(multiprocessing.Process):

    def __init__(self, task_queue, result_queue):
        multiprocessing.Process.__init__(self)
        self.task_queue = task_queue
        self.result_queue = result_queue
        #other initialization stuff

    def run(self):
        caffe.set_mode_gpu()
        caffe.set_device(0)
        #Load caffe net -- code omitted 
        while True:
            image = self.task_queue.get()
            #crop image -- code omitted
            text = net.predict(image)
            self.result_queue.put(text)

        return

import cv2
import caffe
import multiprocessing
import Queue 

tasks = multiprocessing.Queue()
results = multiprocessing.Queue()
consumer = Consumer(tasks,results)
consumer.start()

#Creating window and starting video capturer from camera
cv2.namedWindow("preview")
vc = cv2.VideoCapture(0)
#Try to get the first frame
if vc.isOpened():
    rval, frame = vc.read()
else:
    rval = False
frame_copy[:] = frame
task_empty = True
while rval:
    if task_empty:
       tasks.put(frame_copy)
       task_empty = False
    if not results.empty():
       text = results.get()
       #Add text to frame
       cv2.putText(frame,text)
       task_empty = True

    #Showing the frame with all the applied modifications
    cv2.imshow("preview", frame)

    #Getting next frame from camera
    rval, frame = vc.read()
    frame_copy[:] = frame
    #Getting keyboard input 
    key = cv2.waitKey(1)
    #exit on ESC
    if key == 27:
        break

Je suis assez sûr que c'est le caffe prédiction ralentissement de tout, parce que quand je commenter la prédiction et passer un texte factice et-vient entre les processus, j'ai 30 fps à nouveau.

class Consumer(multiprocessing.Process):

    def __init__(self, task_queue, result_queue):
        multiprocessing.Process.__init__(self)
        self.task_queue = task_queue
        self.result_queue = result_queue
        #other initialization stuff

    def run(self):
        caffe.set_mode_gpu()
        caffe.set_device(0)
        #Load caffe net -- code omitted
        while True:
            image = self.task_queue.get()
            #crop image -- code omitted
            #text = net.predict(image)
            text = "dummy text"
            self.result_queue.put(text)

        return

import cv2
import caffe
import multiprocessing
import Queue 

tasks = multiprocessing.Queue()
results = multiprocessing.Queue()
consumer = Consumer(tasks,results)
consumer.start()

#Creating window and starting video capturer from camera
cv2.namedWindow("preview")
vc = cv2.VideoCapture(0)
#Try to get the first frame
if vc.isOpened():
    rval, frame = vc.read()
else:
    rval = False
frame_copy[:] = frame
task_empty = True
while rval:
    if task_empty:
       tasks.put(frame_copy)
       task_empty = False
    if not results.empty():
       text = results.get()
       #Add text to frame
       cv2.putText(frame,text)
       task_empty = True

    #Showing the frame with all the applied modifications
    cv2.imshow("preview", frame)

    #Getting next frame from camera
    rval, frame = vc.read()
    frame_copy[:] = frame
    #Getting keyboard input 
    key = cv2.waitKey(1)
    #exit on ESC
    if key == 27:
        break

4voto

Dale Points 1578

Quelques Explications et Quelques Repense:

  1. J'ai couru mon code ci-dessous sur un ordinateur portable avec un Intel Core i5-6300HQ @2.3GHz cpu, 8 GB RAM et NVIDIA GeForce GTX 960M gpu(2 go de mémoire), et le résultat a été:

    Si j'ai couru le code avec caffe cours d'exécution ou non(en commentaire ou non, net_output = this->net_->Forward(net_input) et certains trucs nécessaires en void Consumer::entry()- )), je pouvais toujours chercher autour de 30 fps dans le thread principal.

    Le résultat similaire a été obtenu sur un PC avec un Intel Core i5-4440 cpu, 8 GB RAM, NVIDIA GeForce GT 630 gpu(1 go de mémoire).

  2. J'ai couru le code de @user3543300 à la question sur le même ordinateur portable, le résultat a été:

    Si caffe a été en cours d'exécution(sur gpu) ou pas, j'ai également pu obtenir autour de 30 fps.

  3. Selon @user3543300 's feedback, avec les 2 versions du code mentionné ci-dessus, @user3543300 pourrait obtenir seulement d'environ 15 fps, lors de l'exécution de caffe(sur un Nvidia GeForce 940MX GPU and Intel® Core™ i7-6500U CPU @ 2.50GHz × 4ordinateur portable). Et il y aura également un ralentissement de la fréquence d'images de la webcam quand caffe en cours d'exécution sur gpu comme un programme indépendant.

Donc, je continue de penser que le problème peut être plus éventuellement se situent dans le matériel d'e/S limitaions, tels que les DMA de la bande passante(Ce fil à propos de DMA peut soupçon.) ou de la RAM de la bande passante. Espérons @user3543300 pouvez le vérifier ou trouver le vrai problème que je n'ai pas réalisé d'.

Si le problème est en effet ce que je pense de ci-dessus, une sensible la pensée, ce serait la réduction de la mémoire d'e/S overhead introduit par le réseau CNN. En fait, pour résoudre le problème similaire sur des systèmes embarqués avec peu de ressources matérielles, il y a eu peu de recherche sur ce sujet, par exemple Qautization Structurellement Éparses Profonde des Réseaux de Neurones, SqueezeNet, Profonde de Compression. Donc, j'espère, il sera également aider à améliorer le taux de webcam dans la question de l'application de ces compétences.


Réponse Originale À Cette Question:

Essayez cette c++ solution. Il utilise des threads pour les I/O de frais généraux dans votre tâche, je l'ai testé à l'aide de bvlc_alexnet.caffemodel, de déployer.prototxt faire de classification d'images et ne voyaient pas évident de ralentir le thread principal(webcam stream) lorsque caffe en cours d'exécution(sur GPU):

#include <stdio.h>
#include <iostream>
#include <string>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include "caffe/caffe.hpp"
#include "caffe/util/blocking_queue.hpp"
#include "caffe/data_transformer.hpp"
#include "opencv2/opencv.hpp"

using namespace cv;

//Queue pair for sharing image/results between webcam and caffe threads
template<typename T>
class QueuePair {
  public:
    explicit QueuePair(int size);
    ~QueuePair();

    caffe::BlockingQueue<T*> free_;
    caffe::BlockingQueue<T*> full_;

  DISABLE_COPY_AND_ASSIGN(QueuePair);
};
template<typename T>
QueuePair<T>::QueuePair(int size) {
  // Initialize the free queue
  for (int i = 0; i < size; ++i) {
    free_.push(new T);
  }
}
template<typename T>
QueuePair<T>::~QueuePair(){
  T *data;
  while (free_.try_pop(&data)){
    delete data;
  }
  while (full_.try_pop(&data)){
    delete data;
  }
}
template class QueuePair<Mat>;
template class QueuePair<std::string>;

//Do image classification(caffe predict) using a subthread
class Consumer{
  public:
    Consumer(boost::shared_ptr<QueuePair<Mat>> task
           , boost::shared_ptr<QueuePair<std::string>> result);
    ~Consumer();
    void Run();
    void Stop();
    void entry(boost::shared_ptr<QueuePair<Mat>> task
             , boost::shared_ptr<QueuePair<std::string>> result);

  private:
    bool must_stop();

    boost::shared_ptr<QueuePair<Mat> > task_q_;
    boost::shared_ptr<QueuePair<std::string> > result_q_;

    //caffe::Blob<float> *net_input_blob_;
    boost::shared_ptr<caffe::DataTransformer<float> > data_transformer_;
    boost::shared_ptr<caffe::Net<float> > net_;
    std::vector<std::string> synset_words_;
    boost::shared_ptr<boost::thread> thread_;
};
Consumer::Consumer(boost::shared_ptr<QueuePair<Mat>> task
                 , boost::shared_ptr<QueuePair<std::string>> result) :
 task_q_(task), result_q_(result), thread_(){

  //for data preprocess
  caffe::TransformationParameter trans_para;
  //set mean
  trans_para.set_mean_file("/path/to/imagenet_mean.binaryproto");
  //set crop size, here is cropping 227x227 from 256x256
  trans_para.set_crop_size(227);
  //instantiate a DataTransformer using trans_para for image preprocess
  data_transformer_.reset(new caffe::DataTransformer<float>(trans_para
                        , caffe::TEST));

  //initialize a caffe net
  net_.reset(new caffe::Net<float>(std::string("/path/to/deploy.prototxt")
           , caffe::TEST));
  //net parameter
  net_->CopyTrainedLayersFrom(std::string("/path/to/bvlc_alexnet.caffemodel"));

  std::fstream synset_word("path/to/caffe/data/ilsvrc12/synset_words.txt");
  std::string line;
  if (!synset_word.good()){
    std::cerr << "synset words open failed!" << std::endl;
  }
  while (std::getline(synset_word, line)){
    synset_words_.push_back(line.substr(line.find_first_of(' '), line.length()));
  }
  //a container for net input, holds data converted from cv::Mat
  //net_input_blob_ = new caffe::Blob<float>(1, 3, 227, 227);
}
Consumer::~Consumer(){
  Stop();
  //delete net_input_blob_;
}
void Consumer::entry(boost::shared_ptr<QueuePair<Mat>> task
    , boost::shared_ptr<QueuePair<std::string>> result){

  caffe::Caffe::set_mode(caffe::Caffe::GPU);
  caffe::Caffe::SetDevice(0);

  cv::Mat *frame;
  cv::Mat resized_image(256, 256, CV_8UC3);
  cv::Size re_size(resized_image.cols, resized_image.rows);

  //for caffe input and output
  const std::vector<caffe::Blob<float> *> net_input = this->net_->input_blobs();
  std::vector<caffe::Blob<float> *> net_output;

  //net_input.push_back(net_input_blob_);
  std::string *res;

  int pre_num = 1;
  while (!must_stop()){
    std::stringstream result_strm;
    frame = task->full_.pop();
    cv::resize(*frame, resized_image, re_size, 0, 0, CV_INTER_LINEAR);
    this->data_transformer_->Transform(resized_image, *net_input[0]);
    net_output = this->net_->Forward();
    task->free_.push(frame);

    res = result->free_.pop();
    //Process results here
    for (int i = 0; i < pre_num; ++i){
      result_strm << synset_words_[net_output[0]->cpu_data()[i]] << " " 
                  << net_output[0]->cpu_data()[i + pre_num] << "\n";
    }
    *res = result_strm.str();
    result->full_.push(res);
  }
}

void Consumer::Run(){
  if (!thread_){
    try{
      thread_.reset(new boost::thread(&Consumer::entry, this, task_q_, result_q_));
    }
    catch (std::exception& e) {
      std::cerr << "Thread exception: " << e.what() << std::endl;
    }
  }
  else
    std::cout << "Consumer thread may have been running!" << std::endl;
};
void Consumer::Stop(){
  if (thread_ && thread_->joinable()){
    thread_->interrupt();
    try {
      thread_->join();
    }
    catch (boost::thread_interrupted&) {
    }
    catch (std::exception& e) {
      std::cerr << "Thread exception: " << e.what() << std::endl;
    }
  }
}
bool Consumer::must_stop(){
  return thread_ && thread_->interruption_requested();
}


int main(void)
{
  int max_queue_size = 1000;
  boost::shared_ptr<QueuePair<Mat>> tasks(new QueuePair<Mat>(max_queue_size));
  boost::shared_ptr<QueuePair<std::string>> results(new QueuePair<std::string>(max_queue_size));

  char str[100], info_str[100] = " results: ";
  VideoCapture vc(0);
  if (!vc.isOpened())
    return -1;

  Consumer consumer(tasks, results);
  consumer.Run();

  Mat frame, *frame_copy;
  namedWindow("preview");
  double t, fps;

  while (true){
    t = (double)getTickCount();
    vc.read(frame);

    if (waitKey(1) >= 0){
      consuer.Stop();
      break;
    }

    if (tasks->free_.try_peek(&frame_copy)){
      frame_copy = tasks->free_.pop();
      *frame_copy = frame.clone();
      tasks->full_.push(frame_copy);
    }
    std::string *res;
    std::string frame_info("");
    if (results->full_.try_peek(&res)){
      res = results->full_.pop();
      frame_info = frame_info + info_str;
      frame_info = frame_info + *res;
      results->free_.push(res);
    }    

    t = ((double)getTickCount() - t) / getTickFrequency();
    fps = 1.0 / t;

    sprintf(str, " fps: %.2f", fps);
    frame_info = frame_info + str;

    putText(frame, frame_info, Point(5, 20)
         , FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));
    imshow("preview", frame);
  }
}

Et dans src/caffe/util/blocking_queue.cpp, faire un peu de changement ci-dessous et de reconstruire caffe:

...//Other stuff
template class BlockingQueue<Batch<float>*>;
template class BlockingQueue<Batch<double>*>;
template class BlockingQueue<Datum*>;
template class BlockingQueue<shared_ptr<DataReader::QueuePair> >;
template class BlockingQueue<P2PSync<float>*>;
template class BlockingQueue<P2PSync<double>*>;
//add these 2 lines below
template class BlockingQueue<cv::Mat*>;
template class BlockingQueue<std::string*>;

2voto

Shai Points 24484

Il semble que caffe du wrapper python bloque le Mondial Interprète de Verrouillage (GIL). Ainsi, l'appel à la caffe python blocs de commande TOUS les threads python.

Une solution de contournement (à vos risques et périls) serait de désactiver le GIL pour certains caffe fonctions. Par exemple, si vous voulez être en mesure d'exécuter forward sans serrure, vous pouvez éditer $CAFFE_ROOT/python/caffe/_caffe.cpp. Ajouter cette fonction:

void Net_Forward(Net<Dtype>& net, int start, int end) {
  Py_BEGIN_ALLOW_THREADS;   // <-- disable GIL
  net.ForwardFromTo(start, end);
  Py_END_ALLOW_THREADS;     // <-- restore GIL
}

Et remplacez - .def("_forward", &Net<Dtype>::ForwardFromTo) avec:

.def("_forward", &Net_Forward)

N'oubliez pas d' make pycaffe après le changement.

Voir ce pour plus de détails.

0voto

MD. Nazmul Kibria Points 896

On pense pourrait se produire dans votre code, c'est qu'il fonctionne en mode gpu pour le premier appel et lors des appels ultérieurs, il calcule la classification en vertu de l'uc en mode comme le mode par défaut. Sur une ancienne version de caffe jeu de mode gpu pour une fois c'était assez, maintenant version plus récente il a besoin pour régler le mode à chaque fois. Vous pouvez essayer avec la modification suivante:

def run(self):

        #Load caffe net -- code omitted 
        while True:
            caffe.set_mode_gpu()
            caffe.set_device(0)
            image = self.task_queue.get()
            #crop image -- code omitted
            text = net.predict(image)
            self.result_queue.put(text)

        return

Aussi, jetez un oeil à la gpu timings tandis que le thread consommateur est en cours d'exécution. Vous pouvez utiliser la commande suivante pour nvidia:

nvidia-smi

Commande ci-dessus va vous montrer le gpu d'utilisation au moment de l'exécution.

Si cela ne résout une autre solution est de rendre l'utilisation d'opencv cadre de l'extraction de code dans un thread. Comme il est en relation avec les I/O et l'accès au périphérique, vous pouvez obtenir des avantages à le faire fonctionner sur thread séparé de thread GUI/thread principal. Ce thread va pousser de cadres dans une file d'attente et les consommateurs, le thread va prévoir. Dans ce cas, de les manipuler avec soin la file d'attente avec la critique du bloc.

0voto

MD. Nazmul Kibria Points 896

Essayez l'approche multithread au lieu du multitraitement. Le processus de frai est plus lent que le frai dans les threads. Une fois qu'ils fonctionnent, il n'y a pas beaucoup de différence. Dans votre cas, je pense que l'approche de filetage bénéficiera car il y a tellement de données de trames impliquées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X