如何在Python中实现管道化?

时间:2019-06-25 16:39:03

标签: python opencv multiprocessing pipelining

我有一个程序,可以处理一些标记的实时视频。

它分为:

  1. 导入视频的下一张图片
  2. 将图像转换为可读格式
  3. 标记检测
  4. 标记跟踪
  5. 绘图界面

这在我的PC上运行得很好,但是它也需要在Raspberry Pi上运行,因此始终只使用一个内核就不会削减它。

这就是为什么我要引入流水线。 在大学的计算机体系结构课程中,我了解了硬件流水线技术,因此我想知道是否有可能在python中实现类似的东西:

所以不要做 导入->转换-​​>处理->跟踪->绘制-> ...

我想这样做:

class BlockScrollView: UIScrollView {
    var canScroll = true

    override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
        if canScroll {
            super.setContentOffset(contentOffset, animated: animated)
        }
    }
}

以便每个“时钟周期”都准备好图像,而不仅仅是每5个。

所以我当时正在考虑为此使用python的Multiprocessing库,但是我没有经验,但是有一些简单的测试程序,所以不确定是否最适合此用例的是Queue,Pool,Manager,...。 / p>

已解决:

这可以通过mpipe来完成,这是一个很酷的python流水线工具套件。 [http://vmlaker.github.io/mpipe/][1]

-1----2----3----4-----5----...
Imp--Imp--Imp--Imp---Imp---...
-----Conv-Conv-Conv--Conv--...
----------Pro--Pro---Pro---...
---------------Track-Track-...
---------------------Draw--...

正如@r_e所建议的那样,我在一开始就读取了多个图像,并用它填充了一个管道。现在,在计算的每个步骤中,都会启动多个工作进程,以便每个人都可以在单独的图像上工作。

由于除了图像之外还需要传递一些其他信息,因此我只返回图像和其他信息,并在下一阶段再次将其解压缩。

此刻,我不得不禁用跟踪,以至于无法将其与旧版本进行比较。 Atm有点慢(跟踪不会提高速度,因为我不需要每隔30帧就检测一次物体)。但是如果我能使它工作,请给我一个更新。

3 个答案:

答案 0 :(得分:1)

因此,在r_e的帮助下,我找到了一个名为mpipe的简洁工具包,可用于通过python进行流水线操作。

在进行测试时,我发现导入和显示图像比转换,处理和绘制UI快得多,所以我仅使用3级管道。

它非常易于使用:

data have;
  do precinct = 1 to 10;
    do date = '01jan2018'd to '31dec2018'd;
      do seq = 1 to 20*ranuni(123);
        length crime $10 location $8;
        crime = scan('theft,assault,robbery,dnd', ceil(4*ranuni(123)));
        location = scan ('public,private', ceil(2*ranuni(123)));
        crime_dt = dhms(date,0,0,floor('24:00't*ranuni(123)));
        output;      
      end;
    end;
  end;
  drop date;
  format crime_dt datetime19.;
run;

* shorter graphs for SO answer;
ods graphics / height=300px; 

proc sgplot data=have;
  title "VBAR all crimes combined by location";
  vbar crime_dt 
  / group=location
    groupdisplay=cluster
  ;

  format crime_dt dtmonyy7.;
run;

proc sgpanel data=have;
  title "VBAR crime * location";
  panelby crime;
  vbar crime_dt 
  / group=location
    groupdisplay=cluster
  ;

  format crime_dt dtmonyy7.;
run;

proc summary data=have noprint;
  class crime_dt crime location;
  format crime_dt dtmonyy7.;
  output out=freqs;
run;

proc sgplot data=freqs;
  title "SERIES all crimes,summary _FREQ_ * location";
  where _type_ = 5;
  series x=crime_dt y=_freq_ / group=location;
  xaxis type=discrete;
run;

proc sgpanel data=freqs;
  title "SERIES all crimes,summary _FREQ_ * crime * location";
  where _type_ = 7;
  panelby crime;
  series x=crime_dt y=_freq_ / group=location;
  rowaxis min=0;
  colaxis type=discrete;
run;

答案 1 :(得分:0)

由于我没有50个声誉,因此无法对此发表评论。我对它也没有经验,但是一点点搜索使我进入了以下网站,该网站讨论了使用Multiprocessing库进行实时和视频处理的问题。希望对您有所帮助。

1)读取帧;将它们放在输入队列中,并为每个队列添加相应的帧号:

  # Check input queue is not full
  if not input_q.full():
     # Read frame and store in input queue
     ret, frame = vs.read()
      if ret:            
        input_q.put((int(vs.get(cv2.CAP_PROP_POS_FRAMES)),frame))

2)从输入队列中取出帧,并以其对应的帧号放到输出中:

while True:
  frame = input_q.get()
frame_rgb = cv2.cvtColor(frame[1], cv2.COLOR_BGR2RGB)
  output_q.put((frame[0], detect_objects(frame_rgb, sess, detection_graph)))

3)如果输出队列不为空,则在输出队列中恢复已处理的帧并进给优先级队列

# Check output queue is not empty
if not output_q.empty():
  # Recover treated frame in output queue and feed priority queue
  output_pq.put(output_q.get())

4)绘制帧,直到输出队列为空

# Check output priority queue is not empty
  if not output_pq.empty():
    prior, output_frame = output_pq.get()
    if prior > countWriteFrame:
      output_pq.put((prior, output_frame))
    else: 
      countWriteFrame = countWriteFrame + 1    
      # Draw something with your frame

5)最后,要停止,请检查输入队列是否为空。如果是,请休息。

if((not ret) & input_q.empty() & 
    output_q.empty() & output_pq.empty()):
  break

可以找到链接HERE

答案 2 :(得分:0)

我对此做了一点尝试。它很大程度上取决于您的图表,并使用5级管道和多处理。从结尾处开始阅读:

def main():
    ...
    ...

#!/usr/bin/env python3

import logging
import numpy as np
from time import sleep
from multiprocessing import Process, Queue

class Stage1(Process):
    """Acquire frames as fast as possible and send to next stage"""
    def __init__(self, oqueue):
        super().__init__()
        # Pick up parameters and store in class variables
        self.oqueue = oqueue      # output queue

    def run(self,):
        # Turn on logging
        logging.basicConfig(level=logging.DEBUG,
                        format='%(created).6f [%(levelname)s] Stage1 %(message)s',
                        filename='log-stage1.txt', filemode='w')
        logging.info('started')

        # Generate frames and send down pipeline
        for f in range(NFRAMES):
            logging.debug('Generating frame %d',f)
            # Generate frame of random stuff
            frame = np.random.randint(0,256,(480,640,3), dtype=np.uint8)
            logging.debug('Forwarding frame %d',f)
            self.oqueue.put(frame)

class Stage2(Process):
    """Read frames from previous stage as fast as possible, process and send to next stage"""
    def __init__(self, iqueue, oqueue):
        super().__init__()
        # Pick up parameters and store in class variables
        self.iqueue = iqueue      # input queue
        self.oqueue = oqueue      # output queue

    def run(self,):
        # Turn on logging
        logging.basicConfig(level=logging.DEBUG,
                        format='%(created).6f [%(levelname)s] Stage2 %(message)s',
                        filename='log-stage2.txt', filemode='w')
        logging.info('started')

        for f in range(NFRAMES):
            # Wait for next frame
            frame = self.iqueue.get()
            logging.debug('Received frame %d', f)
            # Process frame ...

            logging.debug('Forwarding frame %d', f)
            self.oqueue.put(frame)

class Stage3(Process):
    """Read frames from previous stage as fast as possible, process and send to next stage"""
    def __init__(self, iqueue, oqueue):
        super().__init__()
        # Pick up parameters and store in class variables
        self.iqueue = iqueue      # input queue
        self.oqueue = oqueue      # output queue

    def run(self,):
        # Turn on logging
        logging.basicConfig(level=logging.DEBUG,
                        format='%(created).6f [%(levelname)s] Stage3 %(message)s',
                        filename='log-stage3.txt', filemode='w')
        logging.info('started')
        for f in range(NFRAMES):
            # Wait for next frame
            frame = self.iqueue.get()
            logging.debug('Received frame %d', f)
            # Process frame ...

            logging.debug('Forwarding frame %d', f)
            self.oqueue.put(frame)

class Stage4(Process):
    """Read frames from previous stage as fast as possible, process and send to next stage"""
    def __init__(self, iqueue, oqueue):
        super().__init__()
        # Pick up parameters and store in class variables
        self.iqueue = iqueue      # input queue
        self.oqueue = oqueue      # output queue

    def run(self,):
        # Turn on logging
        logging.basicConfig(level=logging.DEBUG,
                        format='%(created).6f [%(levelname)s] Stage4 %(message)s',
                        filename='log-stage4.txt', filemode='w')
        logging.info('started')

        for f in range(NFRAMES):
            # Wait for next frame
            frame = self.iqueue.get()
            logging.debug('Received frame %d', f)
            # Process frame ...

            logging.debug('Forwarding frame %d', f)
            self.oqueue.put(frame)

class Stage5(Process):
    """Read frames from previous stage as fast as possible, and display"""
    def __init__(self, iqueue):
        super().__init__()
        # Pick up parameters and store in class variables
        self.iqueue = iqueue      # input queue

    def run(self,):
        # Turn on logging
        logging.basicConfig(level=logging.DEBUG,
                        format='%(created).6f [%(levelname)s] Stage5 %(message)s',
                        filename='log-stage5.txt', filemode='w')
        logging.info('started')

        for f in range(NFRAMES):
            # Wait for next frame
            frame = self.iqueue.get()
            logging.debug('Displaying frame %d', f)
            # Display frame ...

def main():
    # Create Queues to send data between pipeline stages
    q1_2 = Queue(5)    # queue between stages 1 and 2
    q2_3 = Queue(5)    # queue between stages 2 and 3
    q3_4 = Queue(5)    # queue between stages 3 and 4
    q4_5 = Queue(5)    # queue between stages 4 and 5

    # Create Processes for stages of pipeline
    stages = []
    stages.append(Stage1(q1_2))
    stages.append(Stage2(q1_2,q2_3))
    stages.append(Stage3(q2_3,q3_4))
    stages.append(Stage4(q3_4,q4_5))
    stages.append(Stage5(q4_5))

    # Start the stages
    for stage in stages:
        stage.start()

    # Wait for stages to finish
    for stage in stages:
        stage.join()

if __name__ == "__main__":
    NFRAMES = 1000
    main()

目前,它只是生成一帧随机噪声,并将其向下传递到管道中。它将每个进程记录到一个单独的文件中,由于filemode='w',该文件将为该程序的每次新运行覆盖。您可以看到这样的单个日志:

-rw-r--r--  1 mark  staff  1097820 26 Jun 17:07 log-stage1.txt
-rw-r--r--  1 mark  staff  1077820 26 Jun 17:07 log-stage2.txt
-rw-r--r--  1 mark  staff  1077820 26 Jun 17:07 log-stage3.txt
-rw-r--r--  1 mark  staff  1077820 26 Jun 17:07 log-stage4.txt
-rw-r--r--  1 mark  staff   548930 26 Jun 17:07 log-stage5.txt

然后您可以查看每个进程接收和发送每个帧的时间:

more log-stage1.txt

1561565618.603456 [INFO] Stage1 started
1561565618.604812 [DEBUG] Stage1 Generating frame 0
1561565618.623938 [DEBUG] Stage1 Forwarding frame 0
1561565618.625659 [DEBUG] Stage1 Generating frame 1
1561565618.647139 [DEBUG] Stage1 Forwarding frame 1
1561565618.648173 [DEBUG] Stage1 Generating frame 2
1561565618.687316 [DEBUG] Stage1 Forwarding frame 2

或者在各个阶段跟踪说“框架1”:

pi@pi3:~ $ grep "frame 1$" log*

log-stage1.txt:1561565618.625659 [DEBUG] Stage1 Generating frame 1
log-stage1.txt:1561565618.647139 [DEBUG] Stage1 Forwarding frame 1
log-stage2.txt:1561565618.671272 [DEBUG] Stage2 Received frame 1
log-stage2.txt:1561565618.672272 [DEBUG] Stage2 Forwarding frame 1
log-stage3.txt:1561565618.713618 [DEBUG] Stage3 Received frame 1
log-stage3.txt:1561565618.715468 [DEBUG] Stage3 Forwarding frame 1
log-stage4.txt:1561565618.746488 [DEBUG] Stage4 Received frame 1
log-stage4.txt:1561565618.747617 [DEBUG] Stage4 Forwarding frame 1
log-stage5.txt:1561565618.790802 [DEBUG] Stage5 Displaying frame 1

或按时间顺序将所有日志组合在一起:

sort -g log*

1561565618.603456 [INFO] Stage1 started
1561565618.604812 [DEBUG] Stage1 Generating frame 0
1561565618.607765 [INFO] Stage2 started
1561565618.612311 [INFO] Stage3 started
1561565618.618425 [INFO] Stage4 started
1561565618.618785 [INFO] Stage5 started
1561565618.623938 [DEBUG] Stage1 Forwarding frame 0
1561565618.625659 [DEBUG] Stage1 Generating frame 1
1561565618.640585 [DEBUG] Stage2 Received frame 0
1561565618.642438 [DEBUG] Stage2 Forwarding frame 0