兼容性问题BX1049: 各浏览器通过 window.resizeTo() 方法最终得到的页面可视区域大小存在差异

探讨了使用window.resizeTo()方法在不同浏览器中调整窗口大小时,因浏览器组件尺寸差异导致的可视区域大小不一致问题。

BX1049: 各浏览器通过 window.resizeTo() 方法最终得到的页面可视区域大小存在差异

  • 标准参考

无。

问题描述

虽然所有主流浏览器均支持 window.resizeTo() 方法,但由于各浏览器地址栏、状态栏等尺寸不同,以及对诸如 window.open() 这类方法的支持存在差异,导致通过 window.resizeTo() 方法最终得到的页面可视区域大小存在差异。

造成的影响

若通过 window.resizeTo() 方法调整了窗口外边尺寸的页面布局依赖于某些浏览器下窗口可视区域大小,则可能造成其他浏览器中此页面布局出现差异。

受影响的浏览器

所有浏览器 

问题分析

window.resizeTo() 方法可以通过传入表示宽度和高度的两个整型参数来控制当前 window 对象对应的浏览器窗口的外边尺寸,这个尺寸包括浏览器窗口的标题栏及窗口边框。
虽然 window.resizeTo() 方法目前不属于任何规范,但所有的主流浏览器均支持此方法。

更多关于 window.resizeTo() 方法的信息,参见 MSDN resizeTo Method 及 Mozilla Developer Center window.resizeTo 中的内容。

测试代码:resizeToParent.html

<a href="javascript:void 0;" onclick="window.open('resizeTo.html','','width=300,height=300,location=yes,menubar=no,status=yes,toolbar=no')">resizeTo</a>

测试代码:resizeTo.html

<!DOCTYPE html>
<html>
<head>
<title>resizeTo</title>
<style>
  html, body { margin:0; padding:0; width:100%; height:100%; font:60px 'Trebuchet MS'; }
</style>
<script>
  window.resizeTo(300, 300);
  window.onload = window.onresize = function () {
    document.body.innerHTML = document.body.clientWidth + ', ' + document.body.clientHeight;
  }
</script>
</head>
<body>
</body>
</html>

运行 resizeToParent.html,点击超链接,将弹出一个子窗口,这里通过 window.open() 方法统一使子窗口只显示地址栏与状态栏。子窗口 resizeTo.html 中通过 window.resizeTo() 方法使页面窗口尺寸定为 300px * 300px。最后计算重新调整尺寸后的子窗口的可视区域。

各浏览器中运行效果如下:

IE6 IE71 IE81 Firefox Chrome Safari Opera

虽然通过 window.open() 方法设定了子窗口仅显示地址栏与状态栏,但由于各浏览器地址栏与状态栏的尺寸不同 (Chrome 不具备地址栏),而整个窗口的外边尺寸又均为 300px * 300px,这导致各浏览器子窗口内的可视区域出现差异。
而 Opera 中子窗口并不是一个原生独立的窗口导致子窗口可视区域与其他浏览器相差甚远。

同时,各浏览器对于 window.open() 方法窗口特征 sFeatures 参数支持程度存在差异,这也进一步导致了通过 window.resizeTo() 方法重新调整后的子窗口内可视区域大小在不同浏览器之间出现很大数值差异。

注 1: IE7 IE8 对于子窗口的显示与浏览器选项卡设置有关,这里全部使用浏览器缺省值。


下面查看若直接运行 resizeTo.html 页面,各浏览器对窗口内可视区域的计算结果:

IE6 IE7 IE8 Firefox Chrome Safari1 Opera
可以调整2不可以调整可以调整不可以调整

注 1: Safari 中若移动窗口,则窗口的外边宽度会变为 350px。
注 2: IE7 IE8 开启了多选项卡浏览后,若当前浏览器窗口已开有其他选项卡,此时可能不可以调整尺寸。

解决方案

使用 window.resizeTo() 方法时需谨慎,不要使被调整窗口尺寸的页面内布局完全依赖于某些浏览器中可视区域的大小。

参见

知识库

相关问题

测试环境

操作系统版本:Windows XP Professional SP3
浏览器版本:IE6
IE7
IE8
Firefox 3.6.10
Chrome 8.0.552.0 dev
Safari 5.0.2
Opera 10.63
测试页面:resizeToParent.html
resizeTo.html
本文更新时间:2010-10-13

关键字

resizeTo width height

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ROS2 节点:识别到绿色启动后扫白色面积寻迹,并在指定时间后检测路面黑色字/块并短暂停止 新增:黑块暂停结束后,开启“红球推行(融合白线巡线)” - 在未检测到绿色之前原地等待(不旋转、不前进) - 识别到绿色并连续确认若干帧后进入白色区域质心巡线 - 在运行了指定时间后(默认 91.5s),若在白色道路中间检测到黑色块则停住指定时长(默认 3s)再继续运行 - 暂停结束后开启红球推行:保持白线巡线 + 根据红球位置微调方向、根据红球面积动态减速 发布:/cmd_vel(geometry_msgs/Twist) 订阅:/image_raw(sensor_msgs/Image) 处理后图像发布:/camera/process_image """ import time import rclpy from rclpy.node import Node from sensor_msgs.msg import Image as RosImage from geometry_msgs.msg import Twist from cv_bridge import CvBridge import cv2 import numpy as np class WhiteAreaFollower(Node): def __init__(self): super().__init__('white_area_follower') # ===== 巡线基础参数 ===== self.declare_parameter('desired_area', 20000) # 期望白色区域面积(像素) self.declare_parameter('max_speed', 0.85) self.declare_parameter('min_speed', 0.15) self.declare_parameter('angular_k', 0.012) self.declare_parameter('roi_top_ratio', 0.55) self.declare_parameter('min_area_detect', 600) self.declare_parameter('show_window', False) # ===== 绿色检测参数(HSV) ===== self.declare_parameter('green_h_low', 59) self.declare_parameter('green_s_low', 63) self.declare_parameter('green_v_low', 130) self.declare_parameter('green_h_high', 97) self.declare_parameter('green_s_high', 255) self.declare_parameter('green_v_high', 255) self.declare_parameter('min_green_area', 400) # 绿色最小面积(像素) self.declare_parameter('green_confirm_frames', 4) # 连续帧确认数 # ===== 黑块/黑字暂停参数 ===== self.declare_parameter('stop_after_seconds', 78.5) # 运行多少秒后开始寻找黑块 self.declare_parameter('stop_duration', 3) # 暂停时长(秒) self.declare_parameter('min_black_area', 500) # 黑块最小面积(像素) self.declare_parameter('black_detect_method', 'otsu') # 'otsu' 或 'fixed_v' self.declare_parameter('black_fixed_v', 80) # 若使用 fixed_v,则 V 阈值 self.declare_parameter('center_tolerance_ratio', 0.15) # 黑块质心需位于图像中间 ±ratio # ===== 红球推行参数(新增) ===== # HSV 双区间(绕 0/180) self.declare_parameter('red_h_low', 0) self.declare_parameter('red_h_high', 10) self.declare_parameter('red_h2_low', 160) self.declare_parameter('red_h2_high', 179) self.declare_parameter('red_s_low', 100) self.declare_parameter('red_v_low', 100) self.declare_parameter('min_red_area', 350) # 红球最小面积(像素) self.declare_parameter('max_red_area', 20000) # 红球面积上限(用来近距离降速) self.declare_parameter('red_angular_k', 0.0045) # 红球角速度系数(越大越“跟球”) self.declare_parameter('red_influence', 0.3) # 红球对角速度影响权重(0~1) self.declare_parameter('red_min_linear', 0.08) # 推球时最小线速度下限 self.declare_parameter('red_linear_scale', 0.6) # 推球线速度比例(相对 max_speed) # ===== 读取参数 ===== self.desired_area = float(self.get_parameter('desired_area').value) self.max_speed = float(self.get_parameter('max_speed').value) self.min_speed = float(self.get_parameter('min_speed').value) self.angular_k = float(self.get_parameter('angular_k').value) self.roi_top_ratio = float(self.get_parameter('roi_top_ratio').value) self.min_area_detect = float(self.get_parameter('min_area_detect').value) self.show_window = bool(self.get_parameter('show_window').value) self.green_h_low = int(self.get_parameter('green_h_low').value) self.green_s_low = int(self.get_parameter('green_s_low').value) self.green_v_low = int(self.get_parameter('green_v_low').value) self.green_h_high = int(self.get_parameter('green_h_high').value) self.green_s_high = int(self.get_parameter('green_s_high').value) self.green_v_high = int(self.get_parameter('green_v_high').value) self.min_green_area = int(self.get_parameter('min_green_area').value) self.green_confirm_frames = int(self.get_parameter('green_confirm_frames').value) self.stop_after_seconds = float(self.get_parameter('stop_after_seconds').value) self.stop_duration = float(self.get_parameter('stop_duration').value) self.min_black_area = int(self.get_parameter('min_black_area').value) self.black_detect_method = str(self.get_parameter('black_detect_method').value) self.black_fixed_v = int(self.get_parameter('black_fixed_v').value) self.center_tolerance_ratio = float(self.get_parameter('center_tolerance_ratio').value) # 红球参数 self.red_h_low = int(self.get_parameter('red_h_low').value) self.red_h_high = int(self.get_parameter('red_h_high').value) self.red_h2_low = int(self.get_parameter('red_h2_low').value) self.red_h2_high = int(self.get_parameter('red_h2_high').value) self.red_s_low = int(self.get_parameter('red_s_low').value) self.red_v_low = int(self.get_parameter('red_v_low').value) self.min_red_area = int(self.get_parameter('min_red_area').value) self.max_red_area = int(self.get_parameter('max_red_area').value) self.red_angular_k = float(self.get_parameter('red_angular_k').value) self.red_influence = float(self.get_parameter('red_influence').value) self.red_min_linear = float(self.get_parameter('red_min_linear').value) self.red_linear_scale = float(self.get_parameter('red_linear_scale').value) # ===== 状态机 ===== self.started = False self._green_count = 0 self._pause_triggered = False self._paused_until = None self._start_time = time.monotonic() # 黑块暂停结束后,开启红球推行融合 self._red_push_enabled = False self.bridge = CvBridge() self.sub = self.create_subscription(RosImage, '/image_raw', self.image_callback, 10) self.pub_img = self.create_publisher(RosImage, '/camera/process_image', 10) self.cmd_pub = self.create_publisher(Twist, 'cmd_vel', 10) self.get_logger().info('white_area_follower started (waiting for green to begin)') # ===== 绿色检测 ===== def detect_green(self, hsv, roi_slice=None): lower = np.array([self.green_h_low, self.green_s_low, self.green_v_low]) upper = np.array([self.green_h_high, self.green_s_high, self.green_v_high]) mask = cv2.inRange(hsv, lower, upper) if roi_slice is not None: full = np.zeros_like(mask) full[roi_slice] = mask[roi_slice] mask = full kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) clean = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1) clean = cv2.morphologyEx(clean, cv2.MORPH_CLOSE, kernel, iterations=2) contours, _ = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) max_area = max([cv2.contourArea(c) for c in contours], default=0) return clean, max_area, contours # ===== 红球检测(新增) ===== def detect_red_ball(self, hsv): lower1 = np.array([self.red_h_low, self.red_s_low, self.red_v_low]) upper1 = np.array([self.red_h_high, 255, 255]) lower2 = np.array([self.red_h2_low, self.red_s_low, self.red_v_low]) upper2 = np.array([self.red_h2_high, 255, 255]) mask1 = cv2.inRange(hsv, lower1, upper1) mask2 = cv2.inRange(hsv, lower2, upper2) mask = cv2.bitwise_or(mask1, mask2) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((7, 7), np.uint8)) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None, mask largest = max(contours, key=cv2.contourArea) area = cv2.contourArea(largest) if area < self.min_red_area: return None, mask M = cv2.moments(largest) if M['m00'] == 0: return None, mask cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00']) return (cx, cy, area), mask def image_callback(self, msg: RosImage): try: frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8') except Exception as e: self.get_logger().error(f'cv_bridge convert failed: {e}') return now = time.monotonic() elapsed = now - self._start_time h, w = frame.shape[:2] roi_top = int(h * self.roi_top_ratio) hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 绿色检测(整图) green_mask, green_area, green_contours = self.detect_green(hsv) vis = frame.copy() # 绿色可视化 if green_contours: largest_g = max(green_contours, key=cv2.contourArea) area_g = cv2.contourArea(largest_g) if area_g >= self.min_green_area: x, y, gw, gh = cv2.boundingRect(largest_g) cv2.rectangle(vis, (x, y), (x + gw, y + gh), (0, 255, 0), 2) cv2.putText(vis, f'GREEN {int(area_g)}', (x, max(0, y - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 更新绿色确认计数 if green_area >= self.min_green_area: self._green_count += 1 else: self._green_count = 0 if not self.started and self._green_count >= self.green_confirm_frames: self.started = True self.get_logger().info(f'Green detected for {self.green_confirm_frames} frames, starting white-area following.') twist = Twist() found = False largest = None # 用于黑块搜索 ROI # ===== 暂停期:保持停止 ===== if self._paused_until is not None and now < self._paused_until: twist.linear.x = 0.0 twist.angular.z = 0.0 remaining = self._paused_until - now cv2.putText(vis, f'PAUSED {remaining:.1f}s', (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) else: # 刚结束暂停:开启红球推行 if self._paused_until is not None and now >= self._paused_until: self._paused_until = None self._red_push_enabled = True self.get_logger().info('Pause finished, RED-PUSH enabled, resuming behavior.') if self.started: # ===== 白色检测(ROI 下半区域) ===== lower_w = np.array([0, 0, 200]) upper_w = np.array([180, 60, 255]) white_mask = cv2.inRange(hsv, lower_w, upper_w) roi_mask = np.zeros_like(white_mask) roi_mask[roi_top:h, :] = white_mask[roi_top:h, :] kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) clean = cv2.morphologyEx(roi_mask, cv2.MORPH_OPEN, kernel, iterations=1) clean = cv2.morphologyEx(clean, cv2.MORPH_CLOSE, kernel, iterations=2) contours, _ = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 可视化白色 mask 到 ROI 区域 vis[roi_top:h, :] = cv2.addWeighted(vis[roi_top:h, :], 0.7, cv2.cvtColor(clean, cv2.COLOR_GRAY2BGR)[roi_top:h, :], 0.9, 0) if contours: largest = max(contours, key=cv2.contourArea) area = cv2.contourArea(largest) if area >= self.min_area_detect: M = cv2.moments(largest) if M['m00'] != 0: cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00']) + roi_top cv2.drawContours(vis, [largest + np.array([0, roi_top])], -1, (0, 255, 0), 2) cv2.circle(vis, (cx, cy), 6, (0, 0, 255), -1) cv2.line(vis, (w // 2, h), (cx, cy), (255, 0, 0), 2) # ====== 巡线基础控制 ====== err = cx - (w / 2) ang_white = - float(err) * self.angular_k ratio = min(area / self.desired_area, 2.0) linear_base = self.min_speed + (self.max_speed - self.min_speed) * (1.0 - ratio) linear_base = float(np.clip(linear_base, self.min_speed, self.max_speed)) twist.angular.z = ang_white twist.linear.x = linear_base found = True # ====== 红球推行融合(仅在暂停后启用) ====== if self._red_push_enabled: red_res, red_mask = self.detect_red_ball(hsv) if red_res is not None: rcx, rcy, rarea = red_res # 红球可视化 cv2.circle(vis, (rcx, rcy), 10, (0, 0, 255), -1) cv2.putText(vis, f'RED {int(rarea)}', (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) # 角度融合:白线为主,红球辅助 err_r = rcx - (w / 2) ang_red = - float(err_r) * self.red_angular_k twist.angular.z = (1.0 - self.red_influence) * ang_white + self.red_influence * ang_red # 线速度:越近越慢 near = float(np.clip(rarea / max(1.0, self.max_red_area), 0.0, 1.0)) # 线速度在 [red_min_linear, max_speed*red_linear_scale] 按距离反比缩放 lin_cap = self.max_speed * self.red_linear_scale * (1.0 - near) lin_cap = max(lin_cap, self.red_min_linear) twist.linear.x = min(twist.linear.x, lin_cap) if not found: # 启动后但未检测到白区:停止(不旋转) twist.linear.x = 0.0 twist.angular.z = 0.0 # ===== 黑块/黑字检测:达到时间且未触发过时检测 ===== if (not self._pause_triggered) and (elapsed >= self.stop_after_seconds): # 优先在当前白色最大区域内搜索黑块;若没有白区则在 ROI 全区搜索 if largest is not None: bx, by, bw_box, bh_box = cv2.boundingRect(largest) sx, sy, sw_box, sh_box = bx, by, bw_box, bh_box else: sx, sy, sw_box, sh_box = 0, roi_top, w, h - roi_top pad = 8 x1 = max(0, sx - pad) y1 = max(roi_top, sy - pad) x2 = min(w, sx + sw_box + pad) y2 = min(h, sy + sh_box + pad) if x2 > x1 and y2 > y1: crop = frame[y1:y2, x1:x2] gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) if self.black_detect_method == 'fixed_v': v_crop = hsv[y1:y2, x1:x2, 2] _, black_bin = cv2.threshold(v_crop, self.black_fixed_v, 255, cv2.THRESH_BINARY_INV) black_bin = black_bin.astype('uint8') else: _, th = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) black_bin = cv2.bitwise_not(th) k2 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) black_clean = cv2.morphologyEx(black_bin, cv2.MORPH_OPEN, k2, iterations=1) black_clean = cv2.morphologyEx(black_clean, cv2.MORPH_CLOSE, k2, iterations=2) b_contours, _ = cv2.findContours(black_clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if b_contours: for b in b_contours: b_area = cv2.contourArea(b) if b_area < self.min_black_area: continue BM = cv2.moments(b) if BM['m00'] == 0: continue bcx_local = int(BM['m10'] / BM['m00']) bcy_local = int(BM['m01'] / BM['m00']) bcx = x1 + bcx_local bcy = y1 + bcy_local center_dist = abs(bcx - (w / 2)) if center_dist <= (self.center_tolerance_ratio * w): # 触发暂停(一次性) self._pause_triggered = True self._paused_until = now + self.stop_duration twist.linear.x = 0.0 twist.angular.z = 0.0 self.get_logger().info( f'Black region detected at center (area={int(b_area)}), pausing for {self.stop_duration}s' ) # 可视化黑块(映射回整图坐标) b_shifted = b + np.array([[x1, y1]]) cv2.drawContours(vis, [b_shifted], -1, (0, 0, 255), 2) cv2.putText(vis, f'BLACK {int(b_area)} - PAUSE', (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) break else: # 未启动:等待绿色触发 twist.linear.x = 0.0 twist.angular.z = 0.0 cv2.putText(vis, 'WAITING FOR GREEN...', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2) # ===== 发布速度 ===== try: self.cmd_pub.publish(twist) except Exception as e: self.get_logger().warn(f'publish cmd_vel failed: {e}') # ===== 输出图像(带状态与绿色掩码缩略图) ===== try: gvis = cv2.cvtColor(green_mask, cv2.COLOR_GRAY2BGR) small = cv2.resize(gvis, (int(w * 0.25), int(h * 0.25))) sw, sh = small.shape[1], small.shape[0] vis[0:sh, w - sw:w] = cv2.addWeighted(vis[0:sh, w - sw:w], 0.6, small, 0.9, 0) status_text = ( 'PAUSED' if (self._paused_until is not None and time.monotonic() < self._paused_until) else ('STARTED' if self.started else f'WAITING (green frames: {self._green_count}/{self.green_confirm_frames})') ) extra = ' RED-PUSH:ON' if self._red_push_enabled else '' info_text = f'{status_text}{extra} elapsed:{int(elapsed)}s' cv2.putText(vis, info_text, (10, h - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2) out_msg = self.bridge.cv2_to_imgmsg(vis, encoding='bgr8') out_msg.header.stamp = self.get_clock().now().to_msg() out_msg.header.frame_id = msg.header.frame_id if msg.header.frame_id else 'camera' self.pub_img.publish(out_msg) except Exception as e: self.get_logger().warn(f'publish process_image failed: {e}') if self.show_window: cv2.imshow('white_area_follower', vis) try: if self.started and 'clean' in locals(): cv2.imshow('white_mask', clean) except Exception: pass cv2.waitKey(1) def destroy_node(self): try: if self.show_window: cv2.destroyAllWindows() except Exception: pass super().destroy_node() def main(args=None): rclpy.init(args=args) node = WhiteAreaFollower() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.get_logger().info('shutting down') node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() 这个识别红球的各个参数代表什么,把注释写详细一些
最新发布
11-13
import argparse import csv import os import platform import sys from math import radians, cos, sin, sqrt from pathlib import Path import cv2 import numpy as np import torch FILE = Path(__file__).resolve() ROOT = FILE.parents[0] # YOLOv5 root directory if str(ROOT) not in sys.path: sys.path.append(str(ROOT)) # add ROOT to PATH ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative from ultralytics.utils.plotting import Annotator, colors, save_one_box from models.common import DetectMultiBackend from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams from utils.general import ( LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh, ) from utils.torch_utils import select_device, smart_inference_mode # 全局变量:保存上一帧的Bullet信息 {id: (x, y)} prev_bullets = {} next_bullet_id = 1 # 下一个可用的Bullet ID def generate_tilted_grid(uav_center, img_shape, grid_spacing=20, tilt_angle=8): """生成以UAV为中心、倾斜指定角度的等距网格线""" h, w = img_shape angle_rad = radians(tilt_angle) cos_a, sin_a = cos(angle_rad), sin(angle_rad) horizontal_lines = [] # 倾斜的水平线 vertical_lines = [] # 倾斜的垂直线(与水平线垂直) # 生成水平线(沿y轴方向等距分布) for dy in range(-h, h, grid_spacing): x1, y1 = 0, uav_center[1] + dy x2, y2 = w, uav_center[1] + dy # 旋转坐标(使用修正后的方向) x1_rot = uav_center[0] + (x1 - uav_center[0]) * cos_a + (y1 - uav_center[1]) * sin_a y1_rot = uav_center[1] - (x1 - uav_center[0]) * sin_a + (y1 - uav_center[1]) * cos_a x2_rot = uav_center[0] + (x2 - uav_center[0]) * cos_a + (y2 - uav_center[1]) * sin_a y2_rot = uav_center[1] - (x2 - uav_center[0]) * sin_a + (y2 - uav_center[1]) * cos_a horizontal_lines.append(((x1_rot, y1_rot), (x2_rot, y2_rot))) # 生成垂直线(沿x轴方向等距分布) for dx in range(-w, w, grid_spacing): x1, y1 = uav_center[0] + dx, 0 x2, y2 = uav_center[0] + dx, h # 旋转坐标(使用修正后的方向) x1_rot = uav_center[0] + (x1 - uav_center[0]) * cos_a + (y1 - uav_center[1]) * sin_a y1_rot = uav_center[1] - (x1 - uav_center[0]) * sin_a + (y1 - uav_center[1]) * cos_a x2_rot = uav_center[0] + (x2 - uav_center[0]) * cos_a + (y2 - uav_center[1]) * sin_a y2_rot = uav_center[1] - (x2 - uav_center[0]) * sin_a + (y2 - uav_center[1]) * cos_a vertical_lines.append(((x1_rot, y1_rot), (x2_rot, y2_rot))) return horizontal_lines, vertical_lines def calculate_grid_offset(bullet_center, uav_center, grid_spacing=20, tilt_angle=8): """计算Bullet在倾斜网格中的横、纵向偏移格数""" angle_rad = radians(tilt_angle) cos_a, sin_a = cos(angle_rad), sin(angle_rad) # 确保坐标是Python数值 if isinstance(bullet_center[0], torch.Tensor): bullet_center = (bullet_center[0].item(), bullet_center[1].item()) if isinstance(uav_center[0], torch.Tensor): uav_center = (uav_center[0].item(), uav_center[1].item()) # 计算偏移量 dx = bullet_center[0] - uav_center[0] dy = bullet_center[1] - uav_center[1] # 旋转计算(修正方向) bx_rot = dx * cos_a + dy * sin_a by_rot = -dx * sin_a + dy * cos_a # 计算偏移格数 offset_x = bx_rot / grid_spacing offset_y = by_rot / grid_spacing return round(offset_x, 2), round(offset_y, 2) def predict_next_position(prev_pos, tilt_angle=8, distance=0): """预测Bullet沿8°轨迹运动后的位置(修正方向)""" angle_rad = radians(tilt_angle) # 修正方向:x/y轴移动量取负 dx = -distance * cos(angle_rad) dy = -distance * sin(angle_rad) return (prev_pos[0] + dx, prev_pos[1] + dy) def match_bullets(current_bullets, prev_bullets, tilt_angle=8, distance_threshold=50): """匹配当前帧与上一帧的Bullet(基于8°轨迹和距离阈值)""" matched = {} used_prev_ids = set() # 对当前每个Bullet,寻找最佳匹配的上一帧Bullet for curr_pos in current_bullets: best_match_id = None min_distance = float('inf') # 检查上一帧的每个Bullet for prev_id, prev_pos in prev_bullets.items(): if prev_id in used_prev_ids: continue # 预测上一帧Bullet沿8°轨迹运动到当前帧的位置 predicted_pos = predict_next_position(prev_pos, tilt_angle, distance_threshold) # 计算当前Bullet位置与预测位置的距离 distance = sqrt( (curr_pos[0] - predicted_pos[0]) ** 2 + (curr_pos[1] - predicted_pos[1]) ** 2 ) # 找到距离最近且小于阈值的匹配 if distance < min_distance and distance < distance_threshold: min_distance = distance best_match_id = prev_id if best_match_id is not None: # 匹配成功,沿用旧ID matched[best_match_id] = curr_pos used_prev_ids.add(best_match_id) else: # 无匹配,分配新ID global next_bullet_id matched[next_bullet_id] = curr_pos next_bullet_id += 1 return matched def init_offset_csv(csv_path): """初始化偏移量CSV文件,写入表头(更新为矩形框参数)""" with open(csv_path, mode="w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow([ "图像名称", "帧序号", "UAV中心点X", "UAV中心点Y", "紫色矩形宽(px)", "紫色矩形高(px)", "紫色矩形坐标(x1,y1,x2,y2)", # 替换原“半径”字段 "Bullet全局ID", "Bullet编号", "Bullet中心点X", "Bullet中心点Y", "横向偏移格数", "纵向偏移格数", "网格间距(px)", "倾斜角度(度)" ]) def write_offset_to_csv(csv_path, data): """将偏移数据写入CSV文件""" with open(csv_path, mode="a", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(data) def get_purple_rect(uav_center, rect_width, rect_height, img_shape): """ 【核心修改】以UAV为中心生成紫色矩形框(可自定义宽高) :param uav_center: UAV中心点 (x, y) :param rect_width: 矩形宽度(像素) :param rect_height: 矩形高度(像素) :param img_shape: 图像尺寸 (h, w) :return: 矩形坐标 (x1, y1, x2, y2)(确保不超出图像边界) """ h, w = img_shape x_center, y_center = uav_center # 计算矩形左上角(x1,y1)和右下角(x2,y2):中心向左右扩展“宽/2”,向上下扩展“高/2” x1 = x_center - (rect_width / 2) y1 = y_center - (rect_height / 2) x2 = x_center + (rect_width / 2) y2 = y_center + (rect_height / 2) # 确保矩形不超出图像边界 x1 = max(0, min(x1, w)) y1 = max(0, min(y1, h)) x2 = max(0, min(x2, w)) y2 = max(0, min(y2, h)) return (x1, y1, x2, y2) @smart_inference_mode() def run( weights=ROOT / "weights/yolov5s.pt", # model path or triton URL source=ROOT / "data/images", # file/dir/URL/glob/screen/0(webcam) data=ROOT / "data/coco128.yaml", # dataset.yaml path imgsz=(640, 640), # inference size (height, width) conf_thres=0.25, # confidence threshold iou_thres=0.45, # NMS IOU threshold max_det=1000, # maximum detections per image 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_format=0, # 0 for YOLO, 1 for Pascal-VOC save_csv=True, # 保存偏移量CSV save_conf=False, # save confidences in --save-txt labels save_crop=False, # save cropped prediction boxes nosave=False, # 不保存图像 classes=None, # filter by class: --class 0, or --class 0 2 3 agnostic_nms=False, # class-agnostic NMS augment=False, # augmented inference visualize=False, # visualize features update=False, # update all models project=ROOT / "runs/detect", # 结果保存根目录 name="exp", # 结果子目录名称 exist_ok=False, # existing project/name ok, do not increment 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 vid_stride=1, # video frame-rate stride grid_spacing=20, # 网格间距(像素) tilt_angle=8, # 倾斜角度(度) purple_rect_width=400, # 【新增】紫色矩形宽度(像素) purple_rect_height=300, # 【新增】紫色矩形高度(像素) distance_threshold=200, # Bullet追踪的距离阈值(像素) ): global prev_bullets, next_bullet_id prev_bullets = {} # 重置上一帧Bullet信息 next_bullet_id = 1 # 重置ID计数器 source = str(source) save_img = not nosave and not source.endswith(".txt") # 控制是否保存图像 is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS) is_url = source.lower().startswith(("rtsp://", "rtmp://", "http://", "https://")) webcam = source.isnumeric() or source.endswith(".streams") or (is_url and not is_file) screenshot = source.lower().startswith("screen") if is_url and is_file: source = check_file(source) # download # 目录设置 save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # 结果目录 (save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True) offset_csv_path = save_dir / "bullet_grid_offsets.csv" # 偏移量CSV文件路径 if save_csv: init_offset_csv(offset_csv_path) # 初始化CSV(已更新表头) # 加载模型 device = select_device(device) model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half) stride, names, pt = model.stride, model.names, model.pt imgsz = check_img_size(imgsz, s=stride) # 检查图像尺寸 # 数据加载器 bs = 1 # batch_size if webcam: view_img = check_imshow(warn=True) dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride) bs = len(dataset) elif screenshot: dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt) else: dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride) vid_path, vid_writer = [None] * bs, [None] * bs # 运行推理 model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # 模型预热 seen, windows, dt = 0, [], (Profile(device=device), Profile(device=device), Profile(device=device)) frame_count = 0 # 帧计数器,用于追踪视频帧序号 for path, im, im0s, vid_cap, s in dataset: frame_count += 1 # 递增帧计数 with dt[0]: im = torch.from_numpy(im).to(model.device) im = im.half() if model.fp16 else im.float() # uint8 to fp16/32 im /= 255 # 0 - 255 to 0.0 - 1.0 if len(im.shape) == 3: im = im[None] # 扩展为批次维度 if model.xml and im.shape[0] > 1: ims = torch.chunk(im, im.shape[0], 0) # 推理 with dt[1]: visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False if model.xml and im.shape[0] > 1: pred = None for image in ims: if pred is None: pred = model(image, augment=augment, visualize=visualize).unsqueeze(0) else: pred = torch.cat((pred, model(image, augment=augment, visualize=visualize).unsqueeze(0)), dim=0) pred = [pred, None] else: pred = model(im, augment=augment, visualize=visualize) # NMS with dt[2]: pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) # 处理预测结果 for i, det in enumerate(pred): # 每张图像 seen += 1 if webcam: # 批量处理 p, im0, frame = path[i], im0s[i].copy(), dataset.count s += f"{i}: " else: p, im0, frame = path, im0s.copy(), getattr(dataset, "frame", 0) p = Path(p) # 路径处理 save_path = str(save_dir / p.name) # 可视化结果保存路径 txt_path = str(save_dir / "labels" / p.stem) + ("" if dataset.mode == "image" else f"_{frame}") s += "{:g}x{:g} ".format(*im.shape[2:]) # 打印信息 # 初始化标注器 annotator = Annotator(im0, line_width=line_thickness, example=str(names)) # 分离UAV和Bullet uav_boxes = [] # 存储UAV的 (xyxy, conf) bullet_boxes = [] # 存储Bullet的 (xyxy, conf) purple_rect_coords = None # 紫色矩形框坐标(替换原正方形框) current_bullet_positions = [] # 当前帧Bullet中心点坐标列表 if len(det): # 调整框到原始图像尺寸 det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() # 分类UAV和Bullet for *xyxy, conf, cls in reversed(det): c = int(cls) label = names[c] if label == "UAV": # 假设类别名为"UAV" uav_boxes.append((xyxy, float(conf))) elif label == "bullet": # 假设类别名为"bullet" bullet_boxes.append((xyxy, float(conf))) # 处理UAV和生成紫色矩形框(核心修改:正方形→矩形) if uav_boxes: # 取第一个UAV作为基准 uav_xyxy, uav_conf = uav_boxes[0] # 计算UAV中心点并转换为数值 uav_center = ( (uav_xyxy[0] + uav_xyxy[2]) / 2, # UAV中心X (uav_xyxy[1] + uav_xyxy[3]) / 2 # UAV中心Y ) uav_center = ( uav_center[0].item() if isinstance(uav_center[0], torch.Tensor) else uav_center[0], uav_center[1].item() if isinstance(uav_center[1], torch.Tensor) else uav_center[1] ) # 生成紫色矩形框(以UAV为中心,自定义宽高) img_h, img_w = im0.shape[:2] purple_rect_coords = get_purple_rect( uav_center, purple_rect_width, purple_rect_height, (img_h, img_w) ) x1, y1, x2, y2 = purple_rect_coords # 标注矩形框(显示宽高信息) annotator.box_label( [x1, y1, x2, y2], f"Purple Rect (W={purple_rect_width}, H={purple_rect_height})", color=(128, 0, 128) # 保持紫色 ) # 筛选紫色矩形框内的Bullet并收集中心点 filtered_bullets = [] for bullet_xyxy, bullet_conf in bullet_boxes: bx1, by1, bx2, by2 = bullet_xyxy # 计算Bullet中心点并转换为数值 bullet_center = ( (bx1 + bx2) / 2, (by1 + by2) / 2 ) bullet_center = ( bullet_center[0].item() if isinstance(bullet_center[0], torch.Tensor) else bullet_center[0], bullet_center[1].item() if isinstance(bullet_center[1], torch.Tensor) else bullet_center[1] ) # 判断是否在紫色矩形框内 if x1 <= bullet_center[0] <= x2 and y1 <= bullet_center[1] <= y2: filtered_bullets.append((bullet_xyxy, bullet_conf, bullet_center)) current_bullet_positions.append(bullet_center) # 收集中心点用于匹配 bullet_boxes = filtered_bullets # 生成并绘制倾斜网格(保持不变) horizontal_lines, vertical_lines = generate_tilted_grid( uav_center, (img_h, img_w), grid_spacing, tilt_angle ) for line in horizontal_lines: cv2.line(im0, (int(line[0][0]), int(line[0][1])), (int(line[1][0]), int(line[1][1])), (0, 0, 255), 1) # 红色水平线 for line in vertical_lines: cv2.line(im0, (int(line[0][0]), int(line[0][1])), (int(line[1][0]), int(line[1][1])), (0, 255, 0), 1) # 绿色垂直线 # 匹配当前帧与上一帧的Bullet(核心追踪逻辑,保持不变) matched_bullets = match_bullets( current_bullet_positions, prev_bullets, tilt_angle=tilt_angle, distance_threshold=distance_threshold ) # 更新上一帧Bullet信息为当前帧 prev_bullets = matched_bullets.copy() # 绘制Bullet并标注偏移和全局ID(保持不变) for bullet_idx, (bullet_xyxy, bullet_conf, bullet_center) in enumerate(bullet_boxes, 1): # 找到当前Bullet对应的全局ID bullet_id = None for bid, pos in matched_bullets.items(): if (round(pos[0], 1) == round(bullet_center[0], 1) and round(pos[1], 1) == round(bullet_center[1], 1)): bullet_id = bid break # 计算偏移 offset_x, offset_y = calculate_grid_offset( bullet_center, uav_center, grid_spacing, tilt_angle ) # 标注Bullet信息(包含全局ID) label = f"Bullet {bullet_id} ({offset_x}, {offset_y})" annotator.box_label(bullet_xyxy, label, color=colors(c, True)) # 保存到CSV(更新为矩形框参数) if save_csv: purple_rect_str = f"({x1:.1f},{y1:.1f},{x2:.1f},{y2:.1f})" write_offset_to_csv(offset_csv_path, [ p.name, # 图像名称 frame_count, # 帧序号 round(uav_center[0], 1), # UAV中心X round(uav_center[1], 1), # UAV中心Y purple_rect_width, # 紫色矩形宽 purple_rect_height, # 紫色矩形高 purple_rect_str, # 紫色矩形坐标 bullet_id, # Bullet全局ID(跨帧唯一) bullet_idx, # 本帧内的编号 round(bullet_center[0], 1), # Bullet中心X round(bullet_center[1], 1), # Bullet中心Y offset_x, # 横向偏移格数 offset_y, # 纵向偏移格数 grid_spacing, # 网格间距 tilt_angle # 倾斜角度 ]) # 处理无UAV的情况(更新CSV字段) elif bullet_boxes and save_csv: write_offset_to_csv(offset_csv_path, [ p.name, frame_count, "", "", purple_rect_width, purple_rect_height, "无UAV,无法生成", # 矩形参数 "", "", "", "", "", "", grid_spacing, tilt_angle ]) LOGGER.warning(f"图像 {p.name} 未检测到UAV,无法生成紫色矩形框和计算偏移量") # 显示结果(保持不变) im0 = annotator.result() if view_img: if platform.system() == "Linux" and p not in windows: windows.append(p) cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0]) cv2.imshow(str(p), im0) cv2.waitKey(1) # 1毫秒 # 保存可视化结果(保持不变) if save_img: if dataset.mode == "image": cv2.imwrite(save_path, im0) LOGGER.info(f"可视化结果已保存至: {save_path}") else: # 视频 if vid_path[i] != save_path: vid_path[i] = save_path if isinstance(vid_writer[i], cv2.VideoWriter): vid_writer[i].release() if vid_cap: fps = vid_cap.get(cv2.CAP_PROP_FPS) w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) else: fps, w, h = 30, im0.shape[1], im0.shape[0] save_path = str(Path(save_path).with_suffix(".mp4")) vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h)) vid_writer[i].write(im0) # 打印时间信息(保持不变) LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms") # 输出结果总结(保持不变) t = tuple(x.t / seen * 1e3 for x in dt) LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}" % t) if save_csv: LOGGER.info(f"偏移数据已保存至: {offset_csv_path}") if save_img: LOGGER.info(f"所有可视化结果已保存至: {save_dir}") if update: strip_optimizer(weights[0]) def parse_opt(): parser = argparse.ArgumentParser() parser.add_argument("--weights", nargs="+", type=str, default='/home/wsj/下载/yolov5-master/yolov5-master/best.pt', help="模型路径") parser.add_argument("--source", type=str, default=ROOT / "data/images", help="输入源") parser.add_argument("--data", type=str, default=ROOT / "data/coco128.yaml", help="数据集配置") parser.add_argument("--imgsz", "--img", "--img-size", nargs="+", type=int, default=[640], help="推理尺寸") parser.add_argument("--conf-thres", type=float, default=0.25, help="置信度阈值") parser.add_argument("--iou-thres", type=float, default=0.45, help="NMS阈值") parser.add_argument("--max-det", type=int, default=1000, help="最大检测数") parser.add_argument("--device", default="", help="设备(cpu/cuda)") parser.add_argument("--view-img", action="store_true", help="实时显示结果") parser.add_argument("--save-txt", action="store_true", help="保存txt标签") parser.add_argument("--save-format", type=int, default=0, help="标签格式") parser.add_argument("--save-csv", action="store_true", default=True, help="保存偏移CSV") parser.add_argument("--save-conf", action="store_true", help="保存置信度") parser.add_argument("--save-crop", action="store_true", help="保存裁剪图像") parser.add_argument("--nosave", action="store_true", default=False, help="不保存任何结果") parser.add_argument("--classes", nargs="+", type=int, help="过滤类别") parser.add_argument("--agnostic-nms", action="store_true", help="类别无关NMS") parser.add_argument("--augment", action="store_true", help="增强推理") parser.add_argument("--visualize", action="store_true", help="可视化特征") parser.add_argument("--update", action="store_true", help="更新模型") parser.add_argument("--project", default=ROOT / "runs/detect", help="结果根目录") parser.add_argument("--name", default="exp", help="结果子目录") parser.add_argument("--exist-ok", action="store_true", help="允许覆盖目录") parser.add_argument("--line-thickness", default=3, type=int, help="框线厚度") parser.add_argument("--hide-labels", default=False, action="store_true", help="隐藏标签") parser.add_argument("--hide-conf", default=False, action="store_true", help="隐藏置信度") parser.add_argument("--half", action="store_true", help="半精度推理") parser.add_argument("--dnn", action="store_true", help="使用DNN") parser.add_argument("--vid-stride", type=int, default=1, help="视频步长") parser.add_argument("--grid-spacing", type=int, default=50, help="网格间距") parser.add_argument("--tilt-angle", type=int, default=8, help="倾斜角度(度)") parser.add_argument("--purple-rect-width", type=int, default=300, help="紫色矩形框宽度(像素)") parser.add_argument("--purple-rect-height", type=int, default=100, help="紫色矩形框高度(像素)") parser.add_argument("--distance-threshold", type=int, default=30, help="Bullet追踪的距离阈值(像素)") opt = parser.parse_args() opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # 扩展尺寸 print_args(vars(opt)) return opt def main(opt): check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop")) run(**vars(opt)) if __name__ == "__main__": opt = parse_opt() main(opt)怎么输出和输入图片一样
09-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值