【代码】CenterNet使用(Detection)(demo.py)

本文详细解析了CenterNet在VOC数据集上的运行流程,包括环境搭建、问题解决、代码逐行解析及性能优化技巧。

一、运行demo.py

按照readme里头的创建一个新环境,按照要求安装即可,中间也遇到了不少的问题,比如说一开始装上了torch0.4.1,之后不能安装torchvision,所以又升到了torch1.0,安装完torchvison之后又回退到了torch0.4.1版本。中间也遇到过DCNv2编译失败的情况,把失败的DCNv2删掉后再重新编译,安装了一些必要组件,好像是那个ffi的模块,然后莫名其妙就编译成功了,之后的external也顺利安装成功了。

除此之外,由于demo最后要显示图片,总是显示"X Server没有连接",还费劲安装了X manager。

安装完之后在服务器shell上输入: export DISPLAY= <本地IP>:0.0应该就可以在Xmanager上显示了。X manager上也运行了XStart,填入服务器地址、用户名和密码就可以了,Xstart应该也是要启用才能显示的。前一步可能不是必须的,但是一定要关闭防火墙。

Pycharm上也能够运行远程的shell,Tools——>Start SSH session就可以了。

以上这些都是准备工作,下面先一步一步的解析代码,让他能够在VOC数据集上运行。

运行demo的命令是:python demo.py ctdet --demo ../images --load_model ../models/ctdet_coco_dla_2x.pth

注:

如果遇到ffi的问题: torch.utils.ffi is deprecated.,按照以下方法来做

https://github.com/xingyizhou/CenterNet/issues/7

如果遇到cv2的版本太高,无法显示的话

cv2.error: OpenCV(4.1.1) /io/opencv/modules/highgui/src/window.cpp:627: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'
可以将src/lib/utils/debugger.py中的self.ipynb(215行show_all_imgs)直接设置成False,这样就不会调用imshow,但是可以使用里plt来显示。

如果遇到显示图片卡住:

可以尝试修改src/lib/utils/debugger.py中的show_all_imgs的waitKey。或许会管用

二、解析准备工作

尝试debug,在Pycharm上出现warning: Debugger speedups using cython not found.以及Connected to pydev debugger 错误。

第一步:将Pycharm上面的解释器替换为CenterNet环境中的解释器,位置在anaconda3/envs/CenterNet/bin

第二步:在pycharm_helpers/pydev/中运行python setup_cython.py build_ext --inplace,建立debugger,之后就能够debug了。

之后还出现了Python Console的错误,按照下图修改即可:

,,将Python interpreter修改为CenterNet中的解释器。

三、流程

1. _init_paths:添加.../CenterNet/src/lib(绝对路径)到sys.path中,并优先启用。之后import的根目录就是lib目录了。

2. 引入opts类,opts类就是一个parser,存在self.parser中,方法parse用来预处理参数,方法init用来初始化,接收输入参数。

if __name__ == '__main__':
  opt = opts().init()
  demo(opt)

接收到的参数放到opt中,传入demo函数中。

3. 两句代码建立Detector

from detectors.detector_factory import detector_factory
Detector = detector_factory[opt.task]
detector = Detector(opt)

detector_factory是如下样子的一个字典,Key是名字,Value是各个Detector类。

detector_factory = {
  'exdet': ExdetDetector, 
  'ddd': DddDetector,
  'ctdet': CtdetDetector,
  'multi_pose': MultiPoseDetector, 
}

detector使用opt初始化了一个检测器。由于不是使用的摄像头,接下来执行如下代码:

4.

  else:
    if os.path.isdir(opt.demo):
      image_names = []
      ls = os.listdir(opt.demo)
      for file_name in sorted(ls):
          ext = file_name[file_name.rfind('.') + 1:].lower()
          if ext in image_ext:
              image_names.append(os.path.join(opt.demo, file_name))
    else:
      image_names = [opt.demo]
    
    for (image_name) in image_names:
      ret = detector.run(image_name)
      time_str = ''
      for stat in time_stats:
        time_str = time_str + '{} {:.3f}s |'.format(stat, ret[stat])
      print(time_str)

第一部分获得image_names,就是images文件夹中的图片。

第二部分就是挨个处理图片,detector.run(image_name)

第三部分就是输出时间,time_str

输出结果如下:

 

重点考察detector.run(image_name)。

5. detector.run,在detectors/ctdet.py中。run的部分继承自base_detector。

三、解析

首先要学习parser的知识,这个博文写的很好:https://www.cnblogs.com/lovemyspring/p/3214598.html

修改为以下代码:

  args = ['ctdet',
          '--demo', '../images',
          '--load_model', '../models/ctdet_coco_dla_2x.pth']
  opt = opts().init(args=args)

将demo中的第19行修改后可以不显示图像:

  opt.debug = 0# max(opt.debug, 1)

接下来解析detector.run。

第一部分是建立debugger:

  def run(self, image_or_path_or_tensor, meta=None):
    load_time, pre_time, net_time, dec_time, post_time = 0, 0, 0, 0, 0
    merge_time, tot_time = 0, 0
    debugger = Debugger(dataset=self.opt.dataset, ipynb=(self.opt.debug==3),
                        theme=self.opt.debugger_theme)
    start_time = time.time()
    pre_processed = False

debugger的代码在utils/debugger中。传入的三个参数为:dataset='coco',ipynb=False, theme='white'。

之后debugger初始化,有这些参数。

第二部分是读取图片,存放在image中:

    if isinstance(image_or_path_or_tensor, np.ndarray):
      image = image_or_path_or_tensor
    elif type(image_or_path_or_tensor) == type (''): 
      image = cv2.imread(image_or_path_or_tensor)
    else:
      image = image_or_path_or_tensor['image'][0].numpy()
      pre_processed_images = image_or_path_or_tensor
      pre_processed = True

之后进行预处理,self.scales=[1.0],pre_processed=False,进行self.pre_process,产生images和meta:

第三部分是preprocess,在pre_process方法中:

  def pre_process(self, image, scale, meta=None):
    height, width = image.shape[0:2]
    new_height = int(height * scale)
    new_width  = int(width * scale)
    if self.opt.fix_res:
      inp_height, inp_width = self.opt.input_h, self.opt.input_w
      c = np.array([new_width / 2., new_height / 2.], dtype=np.float32)
      s = max(height, width) * 1.0
    else:
      inp_height = (new_height | self.opt.pad) + 1
      inp_width = (new_width | self.opt.pad) + 1
      c = np.array([new_width // 2, new_height // 2], dtype=np.float32)
      s = np.array([inp_width, inp_height], dtype=np.float32)

    trans_input = get_affine_transform(c, s, 0, [inp_width, inp_height])
    resized_image = cv2.resize(image, (new_width, new_height))
    inp_image = cv2.warpAffine(
      resized_image, trans_input, (inp_width, inp_height),
      flags=cv2.INTER_LINEAR)
    inp_image = ((inp_image / 255. - self.mean) / self.std).astype(np.float32)

    images = inp_image.transpose(2, 0, 1).reshape(1, 3, inp_height, inp_width)
    if self.opt.flip_test:
      images = np.concatenate((images, images[:, :, :, ::-1]), axis=0)
    images = torch.from_numpy(images)
    meta = {'c': c, 's': s, 
            'out_height': inp_height // self.opt.down_ratio, 
            'out_width': inp_width // self.opt.down_ratio}
    return images, meta

首先进行尺度变换,由于尺度只有1.0,不变,产生resized_image,之后self.opt.fix_res = True,将图像尺寸归一化为512*512,产生inp_image,最后还要进行归一化,并且reshape,得到images(Tensor),以及meta(dict),里头有

{c:resized_image的中心位置,

s:最长宽度,

'out_height': inp_height // 下采样率=输出heatmap高度,

'out_width': inp_width // 下采样率}

第四部分进一步处理数据,放入GPU中:

      images = images.to(self.opt.device)
      torch.cuda.synchronize()
      pre_process_time = time.time()
      pre_time += pre_process_time - scale_start_time
      

其中torch.cuda.synchronize()是为了让所有核同步,测得真实时间。

第五部分放入网络中测试,产生输出:

      output, dets, forward_time = self.process(images, return_time=True)

process部分在ctdet.py中:

  def process(self, images, return_time=False):
    with torch.no_grad():
      output = self.model(images)[-1]
      hm = output['hm'].sigmoid_()
      wh = output['wh']
      reg = output['reg'] if self.opt.reg_offset else None
      if self.opt.flip_test:
        hm = (hm[0:1] + flip_tensor(hm[1:2])) / 2
        wh = (wh[0:1] + flip_tensor(wh[1:2])) / 2
        reg = reg[0:1] if reg is not None else None
      torch.cuda.synchronize()
      forward_time = time.time()
      dets = ctdet_decode(hm, wh, reg=reg, K=self.opt.K)
      
    if return_time:
      return output, dets, forward_time
    else:
      return output, dets

首先将images放入model中,就得到output了。output具有三个部分

{'hm': 1*80*128*128,

'reg': 1*2*128*128,

'wh': 1*2*128*128},可以看出来,只有hm(heatmap)是与类别相关的,reg(offset)和wh(width & height)是与类别无关的。

之后使用ctdet_decode进行解码,得到dets,dets是1*100*6的张量。

最终,返回outputs,dets,forward_time。

第六部分对得到得dets进行后处理:

      dets = self.post_process(dets, meta, scale)
      torch.cuda.synchronize()
      post_process_time = time.time()
      post_time += post_process_time - decode_time

      detections.append(dets)

post_process在ctdet.py中出现:

  def post_process(self, dets, meta, scale=1):
    dets = dets.detach().cpu().numpy()
    dets = dets.reshape(1, -1, dets.shape[2])
    dets = ctdet_post_process(
        dets.copy(), [meta['c']], [meta['s']],
        meta['out_height'], meta['out_width'], self.opt.num_classes)
    for j in range(1, self.num_classes + 1):
      dets[0][j] = np.array(dets[0][j], dtype=np.float32).reshape(-1, 5)
      dets[0][j][:, :4] /= scale
    return dets[0]

使用ctdet_post_process进行后处理,

最后得到得dets是一个len=80的张量(列表?)

 其中每个元素是一个N * 5的ndarray。猜测是每个类别中的dets。detections是一个包含dets的列表。

第七部分进行最后的后处理:

    results = self.merge_outputs(detections)
    torch.cuda.synchronize()
    end_time = time.time()
    merge_time += end_time - post_process_time
    tot_time += end_time - start_time

merge_outputs在ctdet.py中:

  def merge_outputs(self, detections):
    results = {}
    for j in range(1, self.num_classes + 1):
      results[j] = np.concatenate(
        [detection[j] for detection in detections], axis=0).astype(np.float32)
      if len(self.scales) > 1 or self.opt.nms:
         soft_nms(results[j], Nt=0.5, method=2)
    scores = np.hstack(
      [results[j][:, 4] for j in range(1, self.num_classes + 1)])
    if len(scores) > self.max_per_image:
      kth = len(scores) - self.max_per_image
      thresh = np.partition(scores, kth)[kth]
      for j in range(1, self.num_classes + 1):
        keep_inds = (results[j][:, 4] >= thresh)
        results[j] = results[j][keep_inds]
    return results

 

文件: import scrapy from demo1.items import Demo1Item import urllib from scrapy import log # BOSS直聘网站爬虫职位 class DemoSpider(scrapy.Spider): # 爬虫名, 启动爬虫时需要的参数*必填 name = 'demo' # 爬取域范围,允许爬虫在这个域名下进行爬取(可选) allowed_domains = ['zhipin.com'] # 爬虫需要的url start_urls = ['https://www.zhipin.com/c101280600/h_101280600/?query=测试'] def parse(self, response): node_list = response.xpath("//div[@class='job-primary']") # 用来存储所有的item字段 # items = [] for node in node_list: item = Demo1Item() # extract() 将xpath对象转换为Unicode字符串 href = node.xpath("./div[@class='info-primary']//a/@href").extract() job_title = node.xpath("./div[@class='info-primary']//a/div[@class='job-title']/text()").extract() salary = node.xpath("./div[@class='info-primary']//a/span/text()").extract() working_place = node.xpath("./div[@class='info-primary']/p/text()").extract() company_name = node.xpath("./div[@class='info-company']//a/text()").extract() item['href'] = href[0] item['job_title'] = job_title[0] item['sa 报错: C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\python.exe "C:\Users\xieqianyun\PyCharm Community Edition 2019.2.5\helpers\pydev\pydevconsole.py" --mode=client --port=55825 import sys; print('Python %s on %s' % (sys.version, sys.platform)) sys.path.extend(['C:\\Users\\xieqianyun\\demo1', 'C:/Users/xieqianyun/demo1']) Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] Type 'copyright', 'credits' or 'license' for more information IPython 7.10.0 -- An enhanced Interactive Python. Type '?' for help. PyDev console: using IPython 7.10.0 Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32 runfile('C:/Users/xieqianyun/demo1/demo1/begin.py', wdir='C:/Users/xieqianyun/demo1/demo1') Traceback (most recent call last): File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\IPython\core\interactiveshell.py", line 3319, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-2-fc5979762143>", line 1, in <module> runfile('C:/Users/xieqianyun/demo1/demo1/begin.py', wdir='C:/Users/xieqianyun/demo1/demo1') File "C:\Users\xieqianyun\PyCharm Community Edition 2019.2.5\helpers\pydev\_pydev_bundle\pydev_umd.py", line 197, in runfile pydev_imports.execfile(filename, global_vars, local_vars) # execute the script File "C:\Users\xieqianyun\PyCharm Community Edition 2019.2.5\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "C:/Users/xieqianyun/demo1/demo1/begin.py", line 3, in <module> cmdline.execute('scrapy crawl demo'.split()) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\cmdline.py", line 145, in execute cmd.crawler_process = CrawlerProcess(settings) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\crawler.py", line 267, in __init__ super(CrawlerProcess, self).__init__(settings) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\crawler.py", line 145, in __init__ self.spider_loader = _get_spider_loader(settings) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\crawler.py", line 347, in _get_spider_loader return loader_cls.from_settings(settings.frozencopy()) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\spiderloader.py", line 61, in from_settings return cls(settings) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\spiderloader.py", line 25, in __init__ self._load_all_spiders() File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\spiderloader.py", line 47, in _load_all_spiders for module in walk_modules(name): File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\site-packages\scrapy\utils\misc.py", line 73, in walk_modules submod = import_module(fullpath) File "C:\Users\xieqianyun\AppData\Local\Programs\Python\Python36\lib\importlib\__init__.py", line 126, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "<frozen importlib._bootstrap>", line 994, in _gcd_import File "<frozen importlib._bootstrap>", line 971, in _find_and_load File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 665, in _load_unlocked File "<frozen importlib._bootstrap_external>", line 678, in exec_module File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed File "C:\Users\xieqianyun\demo1\demo1\spiders\demo.py", line 4, in <module> from scrapy import log ImportError: cannot import name 'log'
<think>我们来实现detection.py文件。该文件将包含以下功能: 1. 加载YOLOv8模型(这里使用Ultralytics提供的YOLO模型)进行人形检测。 2. 提供函数来检测单帧图像中的人体边界框。 3. 提供函数判断检测到的人体边界框是否在预先定义的危险区域内(危险区域由多个点构成的多边形表示)。 注意:由于我们使用PyQt5进行界面绘制,危险区域是用QPoint的列表表示的。而在OpenCV中,我们通常使用numpy数组的点坐标。因此,我们需要进行坐标转换。 步骤: 1. 安装所需库:ultralytics(用于YOLOv8),opencv-python, numpy, shapely(用于几何判断)等。 2. 使用YOLOv8的检测模型,并过滤出'person'类别。 3. 实现一个函数,判断一个点(或一个边界框)是否在多边形区域内。这里我们可以使用OpenCV的pointPolygonTest,或者shapely库。 由于危险区域可能不止一个,我们将遍历所有危险区域,检查是否有任何一个危险区域包含该人体的中心点(或边界框的某个点)。 代码结构: - 加载模型(在类初始化中完成) - 检测人体(返回人体边界框列表) - 检查单个边界框是否在任一危险区域内(返回布尔值) 注意:在界面上绘制的区域坐标是相对于视频标签的,而模型检测的坐标是原始图像坐标。因此,我们需要将危险区域的坐标转换为原始图像坐标的比例,或者将检测到的边界框坐标转换到视频标签的坐标比例?实际上,我们在主应用中绘制区域时,已经保存了相对于视频帧(缩放后)的坐标。但是,在检测时,我们需要将危险区域坐标映射回原始图像坐标。 为了简化,我们可以在保存危险区域时,将其坐标归一化(相对于视频帧的宽高)。然后,在检测时,根据当前帧的尺寸进行缩放。 另一种方法:在绘制危险区域时,记录的是在视频标签(缩放后)的坐标。在检测时,我们需要将检测到的边界框坐标转换到与绘制时相同的坐标系(即缩放后的坐标),然后判断。 但是,由于视频标签的显示大小可能与原始图像大小不同,我们需要知道原始图像的大小和显示区域的大小,然后进行坐标转换。 我们可以在主应用中将危险区域按照原始图像尺寸进行归一化保存。然后在检测时,直接使用原始图像尺寸下的危险区域坐标(多边形)。 具体做法: - 在绘制完成后,我们将多边形点的坐标归一化(除以原始图像的宽和高)。这样保存的是归一化的坐标。 - 在检测时,我们根据当前帧的宽度和高度,将归一化的坐标转换为实际坐标。 然而,在绘制时,我们使用的是显示在标签上的图像(可能被缩放),而原始图像尺寸是固定的(例如640x480)。所以,我们需要在绘制时记录两个坐标系之间的比例关系。 由于这个转换在主应用中比较容易处理,因此我们修改主应用:保存危险区域时,我们保存的是相对于原始图像尺寸的坐标?不,因为原始图像尺寸可能变化(不同的摄像头分辨率)。所以,我们可以在每次绘制时获取原始帧的尺寸,然后归一化。 我们调整策略: - 在绘制时,我们得到的是相对于显示区域的坐标。但是,显示区域和原始图像有一个缩放比例(保持纵横比)。因此,我们需要将绘制的点转换回原始图像坐标。 在detection.py中,我们假设传入的危险区域坐标已经是相对于原始图像尺寸的(即和当前处理的帧尺寸一致)。这样,在主应用中,当用户绘制完成后,我们将绘制的点(相对于显示区域)映射回原始图像的点。 由于这个映射需要知道原始图像在显示时的缩放和裁剪信息,比较复杂。另一种做法:在绘制时,我们直接使用原始图像尺寸进行绘制(不缩放),但是这样用户绘制会不方便。 因此,我们修改:在绘制完成后,我们将多边形点坐标转换为归一化(0-1)的值(相对于原始图像)。这样,无论图像大小如何变化,我们都可以通过乘以当前帧的宽和高来得到实际坐标。 具体步骤(在主应用中): 1. 绘制时,我们得到的是在显示区域(QLabel)上的坐标。 2. 然后,我们根据原始图像在QLabel中的显示位置(因为有保持纵横比,所以可能有黑边)和缩放比例,将坐标转换为原始图像坐标。 3. 归一化:将原始图像坐标除以原始图像的宽度和高度,得到归一化坐标(0-1之间)。 4. 保存归一化坐标。 在检测时,我们从文件中读取归一化的危险区域,然后乘以当前帧的宽度和高度,得到在当前帧中的实际坐标。 因此,在detection.py中,我们只需要将归一化的危险区域乘以当前帧的宽高,然后进行判断。 但是,由于这个detection.py文件主要是做检测,我们不考虑坐标转换,所以我们假设传入的危险区域坐标已经是当前帧尺寸下的坐标。 所以,在主应用中调用检测函数时,需要先将归一化的危险区域坐标转换回当前帧的实际坐标。 为了解耦,我们在detection.py中提供函数:检查一个边界框(xyxy格式)是否在给定的多边形列表中(每个多边形是当前帧尺寸下的点列表)。 因此,detection.py文件如下: </think>下面我将实现 `detection.py` 文件,该文件负责使用 YOLOv8 模型进行人形检测以及区域入侵检测功能: ```python """ detection.py - 人形检测与区域入侵检测模块 """ import cv2 import numpy as np from shapely.geometry import Point, Polygon from ultralytics import YOLO import os class HumanDetector: def __init__(self, model_path='yolov8n.pt'): """ 初始化YOLOv8人形检测器 参数: model_path: YOLOv8模型路径,默认为'yolov8n.pt' """ self.model = self.load_model(model_path) self.class_id = 0 # COCO数据集中'person'的类别ID为0 def load_model(self, model_path): """加载YOLOv8模型""" # 检查模型文件是否存在 if not os.path.exists(model_path): # 如果本地不存在,则从Ultralytics下载预训练模型 print(f"⚠️ 模型文件 {model_path} 不存在,正在下载预训练模型...") from ultralytics.utils.downloads import download download(model=model_path.split('.')[0]) # 加载模型 model = YOLO(model_path) print(f"✅ YOLOv8模型加载成功: {model_path}") return model def detect_humans(self, frame, conf_threshold=0.5): """ 检测图像中的人形 参数: frame: 输入图像 (numpy数组) conf_threshold: 置信度阈值 返回: results: 检测结果列表,每个元素为 [x1, y1, x2, y2, conf] """ # 使用YOLOv8进行推理 results = self.model(frame, verbose=False)[0] # verbose=False不输出额外信息 # 提取人体检测结果 human_detections = [] for box in results.boxes: conf = float(box.conf[0]) class_id = int(box.cls[0]) # 只保留人形检测结果且置信度大于阈值 if class_id == self.class_id and conf > conf_threshold: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) human_detections.append([x1, y1, x2, y2, conf]) return human_detections def crop_human_face(self, frame, detection): """ 从检测到的人体中裁剪人脸区域 参数: frame: 原始图像 detection: 人体检测框 [x1, y1, x2, y2, conf] 返回: face_image: 裁剪后的人脸图像 (None如果没有找到合适的人脸区域) """ x1, y1, x2, y2, _ = detection height = y2 - y1 # 在人体上半部分裁剪人脸区域 (假设人脸在人体上半部) face_region_height = int(height * 0.4) face_y1 = y1 face_y2 = y1 + face_region_height # 确保裁剪区域在图像范围内 h, w = frame.shape[:2] face_y1 = max(0, face_y1) face_y2 = min(h, face_y2) # 裁剪人脸区域 face_image = frame[face_y1:face_y2, x1:x2] # 确保裁剪区域有效 if face_image.size == 0: return None return face_image class AreaIntrusionDetector: def __init__(self): """初始化区域入侵检测器""" self.danger_areas = [] # 存储危险区域多边形 def load_danger_areas(self, areas): """加载危险区域""" self.danger_areas = areas def check_intrusion(self, detection, frame_shape): """ 检测人体是否在危险区域内 参数: detection: 人体检测框 [x1, y1, x2, y2, conf] frame_shape: 原始图像的形状 (height, width) 返回: bool: 是否在危险区域内 danger_area_id: 入侵的危险区域ID """ if not self.danger_areas: return False, -1 # 计算人体框的中心点 x1, y1, x2, y2, _ = detection person_center = Point((x1 + x2) / 2, (y1 + y2) / 2) # 转换为图像坐标系 (PyQt坐标系原点在左上角,Shapely坐标系原点在左下角) h, w = frame_shape[:2] flipped_center = Point(person_center.x, h - person_center.y) # 检查中心点是否在任意危险区域内 for i, area in enumerate(self.danger_areas): # 将QPoint列表转换为多边形 poly_points = [(p.x(), h - p.y()) for p in area] # 翻转y坐标 polygon = Polygon(poly_points) # 检查点是否在多边形内 if polygon.contains(flipped_center): return True, i return False, -1 def draw_danger_areas(self, frame, frame_shape): """ 在图像上绘制危险区域 参数: frame: 原始图像 frame_shape: 原始图像的形状 (height, width) 返回: frame_with_areas: 绘制了危险区域的图像 """ frame_with_areas = frame.copy() h, w = frame_shape[:2] # 绘制每个危险区域 for area in self.danger_areas: # 转换为OpenCV绘制格式 points = [] for p in area: # 翻转y坐标并转换为图像坐标系 cv_x = int(p.x()) cv_y = h - int(p.y()) # 翻转y轴 points.append([cv_x, cv_y]) # 将点转换为NumPy数组 pts = np.array(points, np.int32) pts = pts.reshape((-1, 1, 2)) # 绘制多边形 cv2.polylines(frame_with_areas, [pts], isClosed=True, color=(0, 0, 255), thickness=2) return frame_with_areas if __name__ == "__main__": # 测试代码 detector = HumanDetector() area_detector = AreaIntrusionDetector() # 加载测试图像 test_img = cv2.imread("test.jpg") if test_img is None: print("❌ 测试图像未找到,请创建test.jpg") exit() # 检测人形 detections = detector.detect_humans(test_img) print(f"检测到 {len(detections)} 个人") # 可视化检测结果 for det in detections: x1, y1, x2, y2, conf = det cv2.rectangle(test_img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(test_img, f"Person: {conf:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # 保存结果 cv2.imwrite("detection_result.jpg", test_img) print("✅ 检测结果已保存为 detection_result.jpg") ``` ### 代码详细解释 #### 1. HumanDetector 类 负责使用 YOLOv8 进行人形检测和人脸裁剪: - **`__init__` 方法**: - 初始化 YOLOv8 模型 - 设置人形类别 ID(COCO 数据集中 person 的 ID 为 0) - **`load_model` 方法**: - 检查本地是否存在模型文件,不存在则自动下载 - 加载 YOLOv8 模型 - **`detect_humans` 方法**: - 使用 YOLOv8 检测图像中的人形 - 过滤置信度低于阈值的结果 - 返回检测框坐标和置信度列表 - **`crop_human_face` 方法**: - 从人体检测框中裁剪人脸区域 - 假设人脸位于人体上半部分 - 返回裁剪后的人脸图像 #### 2. AreaIntrusionDetector 类 负责检测人体是否进入危险区域: - **`load_danger_areas` 方法**: - 加载 PyQt 界面划定的危险区域 - **`check_intrusion` 方法**: - 计算人体检测框的中心点 - 使用 Shapely 库检查中心点是否在危险区域内 - 处理坐标系转换(PyQt 左上角原点 → Shapely 左下角原点) - 返回入侵状态和危险区域 ID - **`draw_danger_areas` 方法**: - 在图像上绘制危险区域多边形 - 处理坐标系转换 - 使用 OpenCV 绘制多边形 ### 关键技术与注意事项 1. **坐标系转换**: - PyQt 使用左上角原点坐标系 - Shapely 使用左下角原点坐标系 - 在检测入侵时需要翻转 y 轴坐标 `y_flipped = height - y` 2. **多边形包含检测**: - 使用 Shapely 的 `Polygon.contains(Point)` 方法 - 比 OpenCV 的 `pointPolygonTest` 更灵活,支持不规则多边形 3. **性能优化**: - 只检查人体中心点(而不是整个边界框) - 使用轻量级 YOLOv8n 模型(可升级为 YOLOv8s/m 提高精度) 4. **错误处理**: - 自动下载缺失的模型文件 - 检查裁剪区域有效性(避免空图像) ### 使用方法示例 在主应用中集成检测模块: ```python # main_app.py 中添加 from detection import HumanDetector, AreaIntrusionDetector class MainApp(QMainWindow): def __init__(self): # ...其他初始化... self.human_detector = HumanDetector() self.area_detector = AreaIntrusionDetector() def update_frame(self): # ...获取帧... # 加载危险区域 self.area_detector.load_danger_areas(self.danger_areas) # 检测人形 detections = self.human_detector.detect_humans(frame) # 检查区域入侵 for det in detections: intrusion, area_id = self.area_detector.check_intrusion(det, frame.shape) if intrusion: # 裁剪人脸并识别 face_img = self.human_detector.crop_human_face(frame, det) # ...人脸识别逻辑... ``` ### 依赖安装 运行前需要安装以下依赖: ```bash pip install ultralytics opencv-python numpy shapely ```
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值