#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from collections import deque
def order_points(pts: np.ndarray) -> np.ndarray:
"""
Order a set of four points in the order:
top-left, top-right, bottom-right, bottom-left.
"""
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
class HollowBlackFrameDetector(Node):
def __init__(self):
super().__init__('hollow_black_frame_detector')
self.bridge = CvBridge()
self.min_area = 1000
self.min_ratio = 1.2
self.max_ratio = 1.6
self.history = deque(maxlen=5)
qos = QoSProfile(
depth=1,
reliability=ReliabilityPolicy.BEST_EFFORT,
history=HistoryPolicy.KEEP_LAST
)
self.create_subscription(
Image,
'/image/raw',
self.image_callback,
qos
)
cv2.namedWindow("Hollow Black Frame", cv2.WINDOW_NORMAL)
cv2.resizeWindow("Hollow Black Frame", 640, 480)
self.get_logger().info(
f"HollowBlackFrameDetector initialized: "
f"min_area={self.min_area}, ratio=[{self.min_ratio},{self.max_ratio}]"
)
def image_callback(self, msg: Image):
try:
frame = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
except Exception as e:
self.get_logger().error(f"CvBridge: {e}")
return
output = self.detect_hollow_frame(frame)
cv2.imshow("Hollow Black Frame", output)
cv2.waitKey(1)
def detect_hollow_frame(self, frame: np.ndarray) -> np.ndarray:
"""
Full process: grayscale -> blur -> Canny -> close -> find quads -> draw -> perspective warp -> draw center dot.
"""
output = frame.copy()
# 1. Grayscale + Gaussian blur
gray = cv2.cvtColor(output, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
# 2. Canny edge detection
edges = cv2.Canny(blur, 50, 150)
# 3. Closing to fill gaps
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations=2)
# 4. Find 4‑point contours
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
quads = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area < self.min_area:
continue
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
if len(approx) == 4 and cv2.isContourConvex(approx):
quads.append((area, approx.reshape(4, 2)))
if not quads:
return output
# 5. Draw largest quad
_, quad = max(quads, key=lambda x: x[0])
cv2.polylines(output, [quad.astype(np.int32)], True, (0, 255, 0), 2)
# 6. Perspective warp to rectangle
rect = order_points(quad)
(tl, tr, br, bl) = rect
widthA = np.linalg.norm(br - bl)
widthB = np.linalg.norm(tr - tl)
maxW = max(int(widthA), int(widthB))
heightA = np.linalg.norm(tr - br)
heightB = np.linalg.norm(tl - bl)
maxH = max(int(heightA), int(heightB))
dst = np.array([
[0, 0],
[maxW - 1, 0],
[maxW - 1, maxH - 1],
[0, maxH - 1]
], dtype="float32")
M = cv2.getPerspectiveTransform(rect, dst)
warp = cv2.warpPerspective(frame, M, (maxW, maxH))
# Draw red center dot on warped image
cv2.circle(warp, (maxW // 2, maxH // 2), 5, (0, 0, 255), -1)
cv2.imshow("Warped Frame", warp)
return output
def main(args=None):
rclpy.init(args=args)
node = HollowBlackFrameDetector()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
要通过识别到黑色边框来进行确定中心点,使用openmv进行,帮我修改上面代码来实现要求