def detect_vid(self):
# pass
import torch
model = self.model
output_size = self.output_size
# source = self.img2predict # file/dir/URL/glob, 0 for webcam
imgsz = [640, 640] # inference size (pixels)
conf_thres = 0.25 # confidence threshold
iou_thres = 0.45 # NMS IOU threshold
max_det = 1000 # maximum detections per image
# device = self.device # cuda device, i.e. 0 or 0,1,2,3 or cpu
view_img = False # show results
save_txt = False # save results to *.txt
save_conf = False # save confidences in --save-txt labels
save_crop = False # save cropped prediction boxes
nosave = False # do not save images/videos
classes = None # filter by class: --class 0, or --class 0 2 3
agnostic_nms = False # class-agnostic NMS
augment = False # ugmented inference
visualize = False # visualize features
line_thickness = 3 # bounding box thickness (pixels)
hide_labels = False # hide labels
hide_conf = False # hide confidences
half = False # use FP16 half-precision inference
dnn = False # use OpenCV DNN for ONNX inference
source = str(self.vid_source)
webcam = self.webcam
device = select_device(self.device)
stride, names, pt, jit, onnx = model.stride, model.names, model.pt, model.jit, model.onnx
imgsz = check_img_size(imgsz, s=stride) # check image size
save_img = not nosave and not source.endswith('.txt') # save inference images
# Dataloader
if webcam:
view_img = check_imshow()
cudnn.benchmark = True # set True to speed up constant image size inference
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt and not jit)
bs = len(dataset) # batch_size
else:
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt and not jit)
bs = 1 # batch_size
vid_path, vid_writer = [None] * bs, [None] * bs
# Run inference
if pt and device.type != 'cpu':
#这里的 pt 和 device.type 是两个变量,它们被用来检查两个条件是否同时满足。!= 是一个不等运算符,用于判断 device.type
# 是否不等于字符串 'cpu'。整个条件表达式的意思是:如果变量 pt 为真(非零或非空)且 device.type 的值不是 'cpu',则执
# 行冒号后面的代码块。
model(torch.zeros(1, 3, *imgsz).to(device).type_as(next(model.model.parameters()))) # warmup
#这行代码的作用是使用模型 model 对一个零张量进行前向传播,这个张量的形状是 (1, 3, height, width),其中 height 和
# width 是图像尺寸,*imgsz 是一个星号操作符,用于展开 imgsz 元组中的元素到位置参数。这个操作是在指定的设备 device 上
# 进行的,并且张量的数据类型被设置为与模型参数相同的类型。这个过程通常用于模型预热(warm up),以准备后续的预测任务。
dt, seen = [0.0, 0.0, 0.0], 0
for path, im, im0s, vid_cap, s in dataset:
t1 = time_sync()# 函数用于记录处理开始的时间戳
im = torch.from_numpy(im).to(device)#将图像数据转换为 PyTorch 张量,并移动到指定的设备(CPU 或 GPU)。
im = im.half() if half else im.float() # uint8 to fp16/32根据变量 half 决定是否将图像数据类型转换为
# 半精度浮点数(fp16)或单精度浮点数(fp32)。
im /= 255 # 0 - 255 to 0.0 - 1.0将图像像素值从 0-255 范围缩放到 0.0-1.0 范围
#for 对于 代码首先遍历名为 dataset 的数据集,其中每一项包含路径(path)、原始图像(im)、未标准化的图像(im0s)、视频捕获对象
# (vid_cap)和字符串(s)。然后,代码执行一系列操作来准备数据进行模型推理,包括时间同步、图像转换、归一化以及批量维度的扩展。
if len(im.shape) == 3:#这一行检查变量im的形状长度是否为3。在Python中,图像数据通常以多维数组的形式表示,其中形状长度为3
# 意味着图像数据包含高度、宽度和通道数(通常是RGB颜色通道)。
im = im[None] # expand for batch dim如果图像数据的形状长度为3,这行代码将在图像数据的开头添加一个额外的轴,以便将图像
# 转换为一个四维数组(批处理维度)。这通常是在使用深度学习模型进行批量预测之前进行的预处理步骤
t2 = time_sync()# 函数用于记录处理开始的时间戳
dt[0] += t2 - t1#dt是一个列表,用于累积时间差。这里,t1是上一次时间同步的时间戳。这一行代码更新dt[0]为自上次时间同步以来经过的时间。
# Inference
# visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
pred = model(im, augment=augment, visualize=visualize)
#这一行调用名为model的函数进行模型推断。im是输入图像数据,augment(如果提供)是增强参数,visualize(如果提供)控制是否可视化中间结果
# pred变量将包含模型的输出预测。
t3 = time_sync()#再次调用time_sync函数来记录当前时间,t3变量用于存储这个时间戳。
dt[1] += t3 - t2#更新dt[1]为自上次时间同步以来经过的时间,即模型推断所需的时间
# NMS
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
#这一行使用非极大值抑制(Non-Maximum Suppression, NMS)算法处理模型的输出。NMS是一种用于去除重叠边界框的技术,保留最佳的边界框。
# conf_thres, iou_thres, classes, agnostic_nms和max_det是NMS算法的参数 探测框优化
dt[2] += time_sync() - t3#更新dt[2]为自上次时间同步以来经过的时间,即NMS处理所需的时间
# Second-stage classifier (optional)
# pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
# Process predictions
#整个代码块的逻辑是测量图像输入到模型推断再到NMS处理的总时间,并将这些时间累加到相应的时间差变量中。这通常用于性能分析,以优化模型的运行时间
for i, det in enumerate(pred): # per image
seen += 1
if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f'{i}: '
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
#对于每一个检测 det,增加 seen 计数。
#如果 webcam 为真(即处理实时视频流),则将当前图像路径 path[i]、原始图像副本 im0s[i] 以及当前帧号 dataset.count 分别赋值给 p、im0 和 frame。
#如果 webcam 为假(即处理静态图片集),则将所有图像路径 path 作为一个列表、原始图像副本 im0s 作为一个副本以及当前帧号 getattr(dataset, 'frame',
# 0) 赋值给 p、im0 和 frame。将路径转换为 Path 对象以便后续操作
p = Path(p) # to Path
# save_path = str(save_dir / p.name) # im.jpg
# txt_path = str(save_dir / 'labels' / p.stem) + (
# '' if dataset.mode == 'image' else f'_{frame}') # im.txt
s += '%gx%g ' % im.shape[2:] # print string
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
imc = im0.copy() if save_crop else im0 # for save_crop
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
#根据是否保存裁剪图像,决定是否复制 im0 到 imc。创建 Annotator 实例,用于在图像上绘制边界框和标签
# ,其中 line_width 设置线条宽度,example 设置示例字符串
#代码片段是一个循环结构,用于处理预测结果 pred 中的每个检测(detection)。变量 seen 用于计数已经处理过的图像数量,
# webcam 是一个布尔标志,用来区分是否使用摄像头实时输入数据(batch size 大于等于 1)还是处理预先存储的图片集
# (batch size 小于 1)。
if len(det):#if len(det): 这一行是一个条件语句,它检查 det 变量的长度是否非零。如果 det 是非空的,即至少
# 包含一个检测结果,那么接下来的代码块将被执行。
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()
# 现在rotated_rects包含转换后的RotatedRect对象
#det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() 这行代码使用 scale_coords
# 函数将检测框的坐标从模型输入图像的尺寸 im.shape[2:] 缩放到实际图像的尺寸 im0.shape。.round() 方法用于将缩放
# 后的浮点数坐标四舍五入到最近的整数
#在代码中,det 是一个二维NumPy数组,包含了每个检测结果的信息。det[:, :4] 表示选择 det 数组中所有行的前四列。
# 这些列通常代表检测框的坐标,按照 [x1, y1, x2, y2] 的顺序排列,其中 (x1, y1) 和 (x2, y2) 分别是边界框
# 左上角和右下角的坐标点。
#在执行推理后,原始的检测框坐标可能是相对于模型输入图像的大小定义的,而 scale_coords 函数用于将这些坐标缩放到
# 实际图像的大小。这样做是为了确保检测框在图像上正确显示。最后,.round() 方法用于将缩放后的浮点数坐标四舍五入
# 到最近的整数,以便于绘制和计算。
# Convert xyxy coordinates to corner points
# Print results
for c in det[:, -1].unique():#这行代码遍历det数组的最后一列(索引为-1),.unique()函数返回该列中非
# 重复元素的列表。因此,循环变量c代表det数组最后一列中的唯一类别值
n = (det[:, -1] == c).sum() # detections per class
#对于当前迭代的类别c,(det[:, -1] == c)创建一个布尔数组,其中True表示det数组最后一列的值等于c。
#然后.sum()计算这个布尔数组中True的数量,即属于该类别的检测数量
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
#这里,f-string用于格式化字符串,包含当前类别的检测数量n和通过类索引int(c)从names列表中获取的类名。
# 如果检测数量大于1,则在类名后面加上复数形式的's'。最后,这个字符串被追加到变量s上,该行尾的逗号和空格用于在打印时分隔不同的类别信息。
#for c in det[:, -1].unique(): ... 循环遍历 det 数组中最后一列的唯一值,这一列通常包含类别标签。
# 对于每个独特的类别,计算该类别的检测数量,并将这些信息添加到字符串 s 中 探测到的类组
# Write results
for *xyxy, conf, cls in reversed(det):
#reversed(det)通常用于Python编程语言中,它是一个内置函数,用于反转一个可迭代对象(如列表、字符串等)。在循环结构中,
# reversed(det)的作用是让循环按照相反的顺序遍历集合元素。
# 这一行代码表示对det这个迭代器进行反向遍历,其中*xyxy代表一个变量接收
# 多个值,conf和cls分别代表置信度和类别标签。每次迭代都会取出一个检测结果,包括边界框坐标、置信度和类别标签
if save_txt: # Write to file,这是一个条件语句,用于检查变量save_txt是否为真。如果为真,则执行后续的文件写入操作。
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(
-1).tolist() # normalized xywh
#这行代码首先将边界框坐标转换为PyTorch张量,并调整形状为(1, 4),然后调用函数xyxy2xywh将其转换为标准的
# (xmin, ymin, xmax, ymax)格式,接着除以缩放因子gn进行归一化处理,最后通过.tolist()方法将张量转换为列表形式。
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
#这行代码根据save_conf变量的值决定是否包含置信度信息在内的输出列表。如果save_conf为真,则输出包含类标签、归一化
#边界框坐标和置信度的列表;如果为假,则只输出类标签和归一化边界框坐标的列表。
#综上所述,代码的逻辑是遍历检测结果,对于每个检测结果,根据是否需要保存文本文件来决定是否将其以特定格式写入文件。
# 如果需要保存,还会包括置信度信息。
# with open(txt_path + '.txt', 'a') as f:
#f.write(('%g ' * len(line)).rstrip() % line + '\n')
if save_img or save_crop or view_img: # Add bbox to image
#代码片段中的条件逻辑是一个简单的逻辑判断语句,用于检查变量 save_img、save_crop 和 view_img 中是否至少有一个为真(True)。
# 这里的 or 关键字表示逻辑“或”操作,意味着只要其中任意一个变量为真,整个条件表达式的结果就是真。如果这些变量都为假(False),
# 则条件表达式的结果为假。save_img: 如果设置为真,表示需要保存图像。
# save_crop: 如果设置为真,表示需要保存裁剪后的图像部分。
# view_img: 如果设置为真,表示需要显示图像。
# 这个条件判断是为了决定是否执行添加边界框(bounding box)和标签到图像的操作。如果上述任一变量为真,程序将继续执行注释中提到的操作
# ,即在图像上绘制边界框并附加相应的标签。
c = int(cls) # integer class
label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
annotator.box_label(xyxy, label, color=colors(c, True))
#代码中的 int(cls) 将类(class)转换为整数类型,这通常是因为在某些情况下,类别标签需要作为整数来使用。names[c] 可能是一个包含类
# 别名称的数组或字典,用于获取对应类别的名称。hide_labels 和 hide_conf 似乎是布尔标志,控制是否隐藏类别标签和置信度分数。
# annotator.box_label() 函数负责在图像上绘制边界框和附加标签,colors(c, True) 函数用于根据类别 c 返回颜色
# if save_crop:
# save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg',
# BGR=True)
#for *xyxy, conf, cls in reversed(det): ... 这个循环遍历 det 数组,并将检测框坐标、置信度和类别标签
# 以反向顺序处理。如果设置了保存文本文件的标志 save_txt,则将检测结果以特定格式写入文本文件。如果设置了将边界
# 框添加到图像的标志 save_img 或其他相关标志,则在图像上绘制边界框。
# Print time (inference-only)
LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')
#这行代码使用了Python的logging模块来输出一条信息级别的日志。LOGGER是一个已经定义好的logger对象,info方法用于记录信息级别的日志。
# f-string是Python 3.6引入的字符串插值方式,{s}和{t3 - t2:.3f}是f-string中的表达式,分别代表变量s的值和计算变量t3减去t2后
# 保留三位小数的结果。这行代码的作用是记录某个操作完成并显示所用时间
# Stream results接下来的代码块是用于实时显示结果的
# Save results (image with detections)接下来的代码块是用于保存带有检测结果的图像
im0 = annotator.result()#这里创建了一个变量im0,其值为annotator对象调用result方法的返回值。annotator是
# 处理图像并进行探测框建立的类实例
frame = im0#将annotator.result()的结果赋值给变量frame,这里frame代表原始的图像数据
resize_scale = output_size / frame.shape[0]#算缩放比例resize_scale,output_size是期望的图像高度或宽度
# (根据上下文可能是高度或宽度),frame.shape[0]是原始图像的高度。
frame_resized = cv2.resize(frame, (0, 0), fx=resize_scale, fy=resize_scale)
#使用OpenCV库的cv2.resize函数将frame按照计算出的缩放比例resize_scale进行缩放,fx和fy参数设置为相同的值以保持图像的宽高比不变
cv2.imwrite("images/tmp/single_result_vid.jpg", frame_resized)#将缩放后的图像保存到指定路径的文件中
self.vid_img.setPixmap(QPixmap("images/tmp/single_result_vid.jpg"))
#最后,这行代码将刚才保存的图像设置为图形界面上名为vid_img的组件的像素图,使得图像能够在图形界面上显示出来。QPixmap是Qt框架中用于
# 表示图像的类。
# self.vid_img
# if view_img:
# cv2.imshow(str(p), im0)
# self.vid_img.setPixmap(QPixmap("images/tmp/single_result_vid.jpg"))
# cv2.waitKey(1) # 1 millisecond
if cv2.waitKey(25) & self.stopEvent.is_set() == True:#是OpenCV中的一个函数,用于等待键盘输入,参数25表示等待时间为25毫秒。
# 如果在这段时间内有按键动作,该函数返回按键的ASCII码值;如果没有按键动作,则返回-1。
# self.stopEvent.is_set()是检查一个线程事件对象是否被设置为True状态。这里的self.stopEvent应该是类的一个属性,
# 通常用于控制循环或者线程的停止条件。
#& 运算符在这里用于按位与操作,确保只有当两个条件都为True时,整个表达式的结果才为True。
self.stopEvent.clear()
#如果上述条件为True,执行此语句,将清除之前设置的停止事件,允许程序继续执行后续代码。
self.webcam_detection_btn.setEnabled(True)#重新启用名为webcam_detection_btn的按钮控件,使其可以再次被用户点击。
self.mp4_detection_btn.setEnabled(True)#同样,重新启用名为mp4_detection_btn的按钮控件
self.reset_vid()#调用reset_vid方法,这个方法可能是用来重置视频检测相关的状态或变量
break#退出当前的循环或者中断当前的执行流程。
#这段代码通常用于图像处理或视频分析的程序中,用于响应用户输入来控制程序的运行状态,例如停止视频捕捉或处理,并重置相关的检测按钮和视频状态。
# self.reset_vid()
rotated_rects = [] # 创建一个空列表来存储RotatedRect对象
for i in range(len(det)):
x, y, w, h = det[:, :4]
center = (float(x), float(y))
size = (float(w), float(h))
angle = 0
rotated_rect = cv2.RotatedRect(center, size, angle)
rotated_rects.append(rotated_rect) # 将RotatedRect对象添加到列表中
camera = cv2.VideoCapture(0)
def distance_to_camera(knownWidth, focalLength, perWidth):
return (knownWidth * focalLength) / perWidth
KNOWN_DISTANCE = 30
KNOWN_WIDTH = 14.5
KNOWN_HEIGHT = 12
def calculate_focalDistance(Img_path):
global focalLength # global定义变量focallength焦距
image = cv2.imread(Img_path)
marker = rotated_rect # 返回cv2.minAreaRect(c)的数据
focalLength = (marker[1][0] * KNOWN_DISTANCE) / KNOWN_WIDTH # 得到焦距
print('focalLength = ', focalLength)
# connectWifi()#先建立连接
camera = cv2.VideoCapture(0)
calculate_focalDistance("D:\picture\\red4.jpg")
# Arduino端口
serialPort = "COM3"
baudRate = 9600
ser = serial.Serial(serialPort, baudRate, timeout=0.5)
# 判断在哪个象限
def JudgeQuadrant(axis_x, axis_y):
distanceX = axis_x - 320;
distanceY = axis_y - 240;
if (distanceX < 0 and distanceY < 0):
return 2
if (distanceX > 0 and distanceY < 0):
return 1
if (distanceX < 0 and distanceY > 0):
return 3
if (distanceX > 0 and distanceY > 0):
return 4
# 返回与x轴的夹角
def computeAngle(axis_x, axis_y):
dx = abs(axis_x - 320);
dy = abs(axis_y - 240)
if dx == 0:
return
tanA = dy / dx;
angle1 = math.atan(tanA)
return math.degrees(angle1)
while camera.isOpened(): # 摄像头打开条件下
(grabbed, frame) = camera.read()
# camera.read()开启电脑默认摄像头,参数grabbed为True 或者False,代表有没有读取到图片 第二个参数frame表示截取到一帧的图片
marker = rotated_rects
# 对这一帧图片建框
print(marker)
if marker == 0: # 无检测成果
# cv2.imshow("captureR", frame)
# cv2.destroyWindow("captureR")
# sendMsg("dis:%.0f" % (99999))
continue
inches = distance_to_camera(KNOWN_WIDTH, focalLength, marker[1][0])
# inches英寸
# sendMsg("dis:%.0f" % (inches))
box = cv2.boxPoints(marker)
# 获取矩形四个顶点,浮点型
box = np.int64(box)
# 储存为64位整数
# 画出轮廓
cv2.drawContours(frame, [box], -1, (0, 255, 0), 2)
# 在图像中某一位置显示文字
cv2.putText(frame, "%.2fcm" % (inches),
(frame.shape[1] - 600, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX,
2.0, (0, 255, 0), 3)
# cv2.destroyWindow("captureR")
# 输出图像,并将图像命名为frame
cv2.imshow("capture", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# camera.release()
cv2.destroyWindow("capture")
# client.close()
# (grabbed, frame) = camera.read()
# marker = find_marker(frame) # 获取物体轮廓,具体参考我的第一篇博客
# continue
box = cv2.boxPoints(marker)
# 获取矩形四个顶点,浮点型
box = np.int0(box)
# 取整
# 此轮廓中心 marker[0][0]为 x轴 marker[0][1]为y轴
JudgeQuadrant(marker[0][0], marker[0][1])
# 象限
Quadrant = JudgeQuadrant(marker[0][0], marker[0][1]);
# 角度
if computeAngle(marker[0][0], marker[0][1]) == None:
continue;
Angle = int(computeAngle(marker[0][0], marker[0][1]));
# 下面的舵机,如果与x轴的角度相差超过20度,则根据所在象限控制左右转动
if 90 - Angle > 10:
Quadrant = JudgeQuadrant(marker[0][0], marker[0][1])
ser.write(str.encode(str(Quadrant))
)
# 上面的舵机
Quadrant = JudgeQuadrant(marker[0][0], marker[0][1])
if Angle > 10 and (Quadrant == 1 or Quadrant == 2):
# 若是在1、2象限则控制上面的舵机向下转动
ser.write(str.encode(str(6)))
elif Angle > 10 and (Quadrant == 3 or Quadrant == 4):
# 若是在3、4象限则控制上面的舵机向上转动
ser.write(str.encode(str(5)))
05-30
1598
11-10
8065
07-25
15万+