You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

254 lines
7.8 KiB

import json
from time import sleep
from typing import Dict
from dataclasses import asdict
import cv2
import numpy as np
import signal
import sys
import threading
import models.target
import tcp_Ser
# 定义全局变量
drawing = False # 是否正在绘制
start_point=models.target.Point
end_point = models.target.Point
target_rectangle_dict:Dict[int,models.target.CircleTarget]={}
sigExit=False # 是否退出
#数据广播
sig_broadcast=True
tcp = tcp_Ser.TcpSer("127.0.0.1", 2230)
myb=threading.Thread(target=tcp.run)
myb.start()
def check_exit(sig, frame):
global sigExit
print(f"收到退出信号 sig={sig}")
sigExit=True
sleep(1)
print("程序退出")
sys.exit(0)
#鼠标回调函数
def add_rectangle(event, x, y, flags, param):
global start_point, end_point, drawing
if event == cv2.EVENT_LBUTTONDOWN: # 左键按下
print("左键按下")
start_point = models.target.Point(x,y)
end_point = start_point
drawing = True
elif event == cv2.EVENT_MOUSEMOVE: # 鼠标移动
if drawing:
end_point = models.target.Point(x,y)
elif event == cv2.EVENT_LBUTTONUP: # 左键抬起
print("左键抬起")
drawing = False
end_point = models.target.Point(x,y)
if start_point==end_point:
return
distance = cv2.norm(tuple(start_point), tuple(end_point), cv2.NORM_L2)
if distance<20:
print("距离小于20,无效区域")
return
target_id=len(target_rectangle_dict)
# 圆标靶半径 mm
radius= 20.0
area=models.target.RectangleArea(start_point.x,start_point.y,end_point.x-start_point.x,end_point.y-start_point.y)
new_target=models.target.CircleTarget(target_id,area,radius)
print(f"新增区域[{target_id}] => {start_point, end_point}")
target_rectangle_dict[target_id] = new_target
def draw_rectangle(img):
gray_frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, gray_frame = cv2.threshold(gray_frame, 120, 255, cv2.THRESH_BINARY)
# 高斯滤波
gray_frame = cv2.GaussianBlur(gray_frame, (5, 5), 1)
cv2.imshow('binaryImg', gray_frame)
if len(target_rectangle_dict)==0: return
#上报有新数据的点
once_upload:Dict[int,models.target.CircleTarget]={}
# 绘图-历史点
for i, tr in target_rectangle_dict.items():
_start_point = tr.rectangle_area[0]
_end_point = tr.rectangle_area[1]
#绘制标靶区域
cv2.rectangle(img,tuple(_start_point), tuple(_end_point), (255, 0, 0), 2)
#检测
sub_image = extract_sub_image(gray_frame, _start_point, _end_point)
circles = circle2_detect(sub_image)
if len(circles) == 0:
continue
center,radius=circle_show(img,circles,_start_point)
# 纪录圆心位置
tr.center_point=center
tr.radius_pix=radius
if tr.is_init:
tr.center_init=tr.center_point
tr.is_init=False
tar = tr.circle_displacement()
msg=f"[{tar.id}]displacement_pix={tar.displacement_pix},displacement_phy={tar.displacement_phy}"
print(msg)
once_upload[tr.id]=tr
#过滤无效空数据
if len(once_upload.items())==0: return
json_str = json.dumps(
{k:asdict(v) for k, v in once_upload.items() if v.is_init==False}
)
print(f"标靶数据={json_str}",json_str)
if sig_broadcast:
tcp.broadcast_message(json_str)
def circle_show(img, circles, relative_point:models.target.Point):
font = cv2.FONT_HERSHEY_SIMPLEX
color = (255, 0, 0) # 蓝色
scale = 0.5
circle = max(circles, key=lambda c: c[2])
# print("画圆", circle)
# 绘制圆心
center = (circle[0] + relative_point.x, circle[1] + relative_point.y)
center_int = tuple(int(x) for x in center)
cv2.circle(img, center_int, 2, (0, 255, 0), 4)
radius = np.round(circle[2], 3)
radius_int = int(radius)
# 绘制外圆
cv2.circle(img, center_int, radius_int, (0, 0, 255), 2)
# 打印圆心坐标
text1 = f"center:{circle}"
text2 = f"r:{radius}"
txt_location = (center_int[0] + radius_int, center_int[1] + radius_int // 2)
cv2.putText(img, text1, txt_location, font, scale, color, 2)
cp=models.target.Point(x=center[0], y=center[1])
return cp,radius
def circle2_detect(img):
# 圆心距 canny阈值 最小半径 最大半径
circles_float = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT_ALT, 1.5, 30, param1=300, param2=0.9, minRadius=15,
maxRadius=0)
# 创建一个0行, 2列的空数组
if circles_float is not None:
num_circles = circles_float.shape[1] # 获取检测到的圆的数量
print("圆的数量", num_circles)
# 提取圆心坐标(保留2位小数)
centers = [(round(float(x),2), round(float(y),2), round(float(r),2)) for x, y, r in circles_float[0, :]]
return centers
else:
return []
def extract_sub_image(frame, top_left, bottom_right):
"""
从帧中截取子区域
:param frame: 输入的视频帧
:param top_left: 子图片的左上角坐标 (x1, y1)
:param bottom_right: 子图片的右下角坐标 (x2, y2)
:return: 截取的子图片
"""
x1, y1 = top_left
x2, y2 = bottom_right
return frame[y1:y2, x1:x2]
def open_video(video_id):
cap = cv2.VideoCapture(video_id)
# cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1600) # 宽度
# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 900) # 高度
if not cap.isOpened():
print("无法打开摄像头")
exit()
return cap
def show_video(cap):
global sigExit,start_point, end_point, drawing
cv2.namedWindow('Frame')
cv2.setMouseCallback('Frame', add_rectangle)
# 读取一帧图像
while True:
ret, frame = cap.read()
if ret:
frame_handle(frame)
else:
print("无法读取帧")
if cv2.waitKey(1) & 0xFF == ord('q'): # 按'q'退出循环
break
if sigExit:
break
def show_image(frame):
global start_point, end_point, drawing
cv2.namedWindow('Frame')
cv2.setMouseCallback('Frame', add_rectangle)
# 读取一帧图像
while True:
cp_img=frame.copy()
frame_handle(cp_img)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按'q'退出循环
break
cv2.destroyAllWindows()
def frame_handle(frame):
# 绘图-历史点
draw_rectangle(frame)
# 绘图-实时
if drawing:
cv2.rectangle(frame, tuple(start_point), tuple(end_point), (0, 200, 200), 4)
# print(f"鼠标位置 {start_point} -> {end_point}")
# 显示图像
cv2.imshow('Frame', frame)
# 读取图像
#img_copy = img.copy() # 复制图像用于还原
def video_mode(video_id):
capture = open_video(video_id)
fps = capture.get(cv2.CAP_PROP_FPS)
print(f"fps={fps}")
show_video(capture)
# 释放摄像头资源并关闭所有窗口
capture.release()
cv2.destroyAllWindows()
def image_mode():
img_raw=cv2.imread('images/trans/_4point.jpg')#images/target/rp80max3.jpg
# img_raw = cv2.imread('images/trans/_4point.jpg') # images/target/rp80max3.jpg
# img_raw = cv2.imread('images/target/rp80.jpg') # images/target/rp80max3.jpg
show_image(img_raw)
def rtsp_mode():
# rtsp_url ="rtsp://admin:123456abc@192.168.1.64:554"
rtsp_url ="rtsp://localhost:8554/rtsp"
capture = open_video(rtsp_url)
fps = capture.get(cv2.CAP_PROP_FPS)
print(f"fps={fps}")
show_video(capture)
# 释放摄像头资源并关闭所有窗口
capture.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
signal.signal(signal.SIGINT, check_exit)
rtsp_mode()
# video_mode(0)
# image_mode()
cv2.waitKey(0)
cv2.destroyAllWindows()