之前做的博客《玩机器人,先从一名军火商做起》,讲述了如何设计一个电磁炮,当时的视频给大家演示到了OpenCV的单目标锁定追踪的效果。
但是那个锁定追踪不是用ROS开发的,而只是简单的Python OpenCV + PID + 舵机控制组成的。最近看了古月老师的《ROS机器视觉开发入门》教程,也尝试着使用ROS的方式来开发一下单目标锁定追踪的Demo程序。
果然,古月老师YYDS!实在是强大,调试好了!效果如下。需要功能包的小伙伴可以私信古月居公众号哦~
嗯,步骤呢,先启动usb_cam功能包,调用摄像头然后发布个图像的话题,然后启动object_tracker功能包的object_tracker.py文件,定于图像话题转成OpenCV的数据来操作即可,简直简单的不要不要的~
嗯,看到这里可能有人要骂了,没错,就是这么的凡尔赛~
但是真的很简单,不信,你看下去!
我们先来聊聊关于Python + OpenCV实现单目标锁定追踪是咋搞的!话不多说,上链接!传送门
没猜错,单目标锁定追踪的代码抄这里的,它的代码是这样的!
#!usr/bin/env python3
#coding:utf-8
from imutils.video import VideoStream
from imutils.video import FPS
import argparse
import imutils
import time
import cv2
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video", type=str, help="path to input video file")
ap.add_argument("-t", "--tracker", type=str, default="kcf", help="OpenCV object tracker type")
args = vars(ap.parse_args())
# My opencv:4.1.0
(major, minor) = cv2.__version__.split(".")[:2]
if int(major) == 3 and int(minor) < 3:
tracker = cv2.Tracker_create(args["tracker"].upper())
else:
OPENCV_OBJECT_TRACKERS = {
"csrt": cv2.TrackerCSRT_create,
"kcf": cv2.TrackerKCF_create,
#"boosting": cv2.TrackerBoosting_create,
"mil": cv2.TrackerMIL_create,
#"tld": cv2.TrackerTLD_create,
#"medianflow": cv2.TrackerMedianFlow_create,
#"mosse": cv2.TrackerMOSSE_create
}
tracker = OPENCV_OBJECT_TRACKERS[args["tracker"]]()
initBB = None
if not args.get("video", False):
print("[INFO] starting video stream...")
vs = VideoStream(src=0).start()
time.sleep(1.0)
else:
vs = cv2.VideoCapture(args["video"])
fps = None
while True:
frame = vs.read()
frame = frame[1] if args.get("video", False) else frame
if frame is None:
break
#frame = imutils.resize(frame, height = 1280, width=720)
(H, W) = frame.shape[:2]
if initBB is not None:
(success, box) = tracker.update(frame)
if success:
(x, y, w, h) = [int(v) for v in box]
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
fps.update()
fps.stop()
info = [
("Tracker", args["tracker"]),
("Success", "Yes" if success else "No"),
("FPS", "{:.2f}".format(fps.fps())),
]
for (i, (k, v)) in enumerate(info):
text = "{}: {}".format(k, v)
cv2.putText(frame, text, (10, H - ((i * 20) + 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord("s"):
initBB = cv2.selectROI("Frame", frame, fromCenter=False, showCrosshair=True)
tracker.init(frame, initBB)
fps = FPS().start()
elif key == ord("q"):
break
if not args.get("video", False):
vs.stop()
cv2.destroyAllWindows()
这里我就不加注释了,在后面的ROS代码里面会有超级详细的注释。
实现的原理也比较简单,主要是通过调用Python OpenCV内置的ROI感兴趣区域提取算法来实现。以前的OpenCV支持七个计算模型,现在不知道什么情况,只有三个能用。
呃,具体的原理,大家看下这篇博文吧,我也不想写了,主要是写的不一定有人家写的好。https://blog.csdn.net/weixin_38907560/article/details/82292091
他这个博文讲的代码和我在上面抄的一样的,有着很详细的解释,特别是每个计算模型之间的异同都有介绍~
不过这段代码要运行,你需要安装一下opencv-python、opencv-contrib-python、imutils三个功能包,使用Python3的环境,最后是Python3.6以上的。(往下我没测试过,我的是Python3.8)
pip3 install opencv-python
pip3 install opencv-contrib-python
pip3 install imutils
我们pip在安装的时候,会跳到pypi.org这个网站进行下载,如果pip显示链接超时的情况下,可以去这个网站根据自己的Python版本和名称来下载对应的安装包,离线安装即可。
嗯,其实Python + OpenCV的方式是完全可以开发的,为啥非把这玩意在ROS下再开发一次呢~
我的想法是这样的。假如我现在有台具备摄像头的机器人,我如果使用Python + OpenCV的方式进行开发的话,摄像头的资源只能在这一个程序里面使用,调用摄像头、获取图像、处理图像、输出图像,貌似也没啥问题。
前端时间接了个开发兼职,甲方爸爸提了个项目需求,我现在只有一个摄像头,但是现在有两个程序需要同时使用这个摄像头的数据,不可能两个程序同时调用摄像头来搞,难搞难搞~
最后的解决方案是“跨线程”,开了三条线程,第一个线程专门用来读取摄像头的数据,第二个线程从线程1获取摄像头数据让程序1来使用,第三个线程从线程1获取摄像头数据让程序2来执行,算是勉勉强强的完成了项目需求。功能实现了,后续有优化需求再说,你电脑跑不了是你环境的问题。毕竟程序和程序猿总要有一个得跑的吧(PS:玩笑话)
这是个很有意思的需要,可以在扩充下,我现在是在两个程序,如果有三个四个甚至多个呢?开线程这玩意“治标不治本”,而且自己做的diamagnetic比较烂,安全不安全的根本就没考虑;如果有两个电脑需要这个数据的,那我岂不是还要做个网络通讯吗,实在是不划算。
其实这个问题在ROS下可以得到很好的解决。我们通过usb_cam来获取摄像头数据,将这个数据通过话题的方式发布出去,你们谁要用直接来订阅就好了,和我无关。就像我们这里做的Demo,usb_cam发布一个图像话题之后,我的image_view做了订阅显示,我的object_tracker做了订阅并通过ROS的cv_bridge功能包转成OpenCV的数据类型,直接使用OpenCV来处理数据。
这个rqt_graph是很简洁清晰的,一个名叫usb_cam的节点发布了一个叫做/usb_cam/image_raw的话题,分别被image_view节点和object_tarcker节点订阅。至于这俩节点订阅后干了啥,自己瞅代码去!
这里有个很重要的地方,ROS的图像数据格式和OpenCV的图像数据格式之间的互转,这里用到了cv_bridge功能包,咋实现的我也不知道,在Python语言下能用起来就好,这里抄一份古月老师的代码。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy
import cv2
from cv_bridge import CvBridge, CvBridgeError
from sensor_msgs.msg import Image
class image_converter:
def __init__(self):
# 创建cv_bridge,声明图像的发布者和订阅者
self.image_pub = rospy.Publisher("cv_bridge_image", Image, queue_size=1)
self.bridge = CvBridge()
self.image_sub = rospy.Subscriber("/usb_cam/image_raw", Image, self.callback)
def callback(self,data):
# 使用cv_bridge将ROS的图像数据转换成OpenCV的图像格式
try:
cv_image = self.bridge.imgmsg_to_cv2(data, "bgr8")
except CvBridgeError as e:
print(str(e))
# 在opencv的显示窗口中绘制一个圆,作为标记
(rows,cols,channels) = cv_image.shape
if cols > 60 and rows > 60 :
cv2.circle(cv_image, (60, 60), 30, (0,0,255), -1)
# 显示Opencv格式的图像
cv2.imshow("Image window", cv_image)
cv2.waitKey(3)
# 再将opencv格式额数据转换成ros image格式的数据发布
try:
self.image_pub.publish(self.bridge.cv2_to_imgmsg(cv_image, "bgr8"))
except CvBridgeError as e:
print(str(e))
if __name__ == '__main__':
try:
# 初始化ros节点
rospy.init_node("cv_bridge_test")
rospy.loginfo("Starting cv_bridge_test node")
image_converter()
rospy.spin()
except KeyboardInterrupt:
print("Shutting down cv_bridge_test node.")
cv2.destroyAllWindows()
这个是古月老的cv_bridge_test的代码,功能方面实现了订阅/usb_cam/image_raw话题转成了OpenCV的格式,并通过Python OpenCV进行处理给加了个红色的圈显示处理并显示出来,显示之后又将处理过的图像转成ROS下的图像话题cv_bridge_image给发布到了ROS系统下。
而追踪的ROS代码呢,其实就是这两段代码的结合体,它是这样的!
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rospy #导入rospy
import cv2 #导入cv2
from cv_bridge import CvBridge, CvBridgeError #导入cv_bridge
from sensor_msgs.msg import Image #导入sensor_msgs
import argparse #导入argparse
ap = argparse.ArgumentParser() #读取后缀参数
ap.add_argument("-t", "--tracker", type=str, default="kcf", help="OpenCV object tracker type") #后缀参数t用于指定模型
args = vars(ap.parse_args()) #获取参赛
(major, minor) = cv2.__version__.split(".")[:2] #检测当前OpenCV版本
#版本小于等于3的和大于3的部分参数不一样
if int(major) == 3 and int(minor) < 3:
tracker = cv2.Tracker_create(args["tracker"].upper())
else:
OPENCV_OBJECT_TRACKERS = {
"csrt": cv2.TrackerCSRT_create, #CSRT求解器
"kcf": cv2.TrackerKCF_create, #KCF求解器
#"boosting": cv2.TrackerBoosting_create, #BOOSTING求解器,已弃用
"mil": cv2.TrackerMIL_create, #MIL求解器
#"tld": cv2.TrackerTLD_create, #TLD求解器,已弃用
#"medianflow": cv2.TrackerMedianFlow_create, #MedianFlow求解器,已弃用
#"mosse": cv2.TrackerMOSSE_create #MOSSE求解器,已弃用
}
tracker = OPENCV_OBJECT_TRACKERS[args["tracker"]]() #读取参数加载模型
initBB = None #初始化比对数据空间(ROI数据空间)
class image_converter:
def __init__(self):
# 创建cv_bridge,声明图像的发布者和订阅者
self.image_pub = rospy.Publisher("cv_bridge_image", Image, queue_size=1)
self.bridge = CvBridge()
self.image_sub = rospy.Subscriber("/usb_cam/image_raw", Image, self.callback)
def callback(self,data):
# 使用cv_bridge将ROS的图像数据转换成OpenCV的图像格式
try:
cv_image = self.bridge.imgmsg_to_cv2(data, "bgr8")
except CvBridgeError as e:
print(str(e))
(H, W) = cv_image.shape[:2] #获取图像的Height和Weight
global initBB
#如果ROI数据不为空,说明有需要追踪的目标
if initBB is not None:
(success, box) = tracker.update(cv_image) #将ROI数据和当前帧进行比对,返回比对结果和ROI区域值
#如果比对结果为真,说明在当前帧图像内检测到需要追踪的目标
if success:
(x, y, w, h) = [int(v) for v in box] #从ROI区域值提取x、y、w、h四个数值
cv2.rectangle(cv_image, (x, y), (x + w, y + h), (0, 255, 0), 2) #画框
#制作提示信息
info = [
("Tracker", args["tracker"]),
("Success", "Yes" if success else "No"),
]
#将提示信息显示在图像上面
for (i, (k, v)) in enumerate(info):
text = "{}: {}".format(k, v)
cv2.putText(cv_image, text, (10, H - ((i * 20) + 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.imshow("cv_image", cv_image) #使用OpenCV进行显示
key = cv2.waitKey(1) & 0xFF #做个按键检测
#如果按下了's',通过鼠标选择要追踪的目标
if key == ord("s"):
initBB = cv2.selectROI("cv_image", cv_image, fromCenter=False, showCrosshair=True)
tracker.init(cv_image, initBB) #将选中的内容传入到initBB
# 再将opencv格式额数据转换成ros image格式的数据发布
try:
self.image_pub.publish(self.bridge.cv2_to_imgmsg(cv_image, "bgr8"))
except CvBridgeError as e:
print(str(e))
if __name__ == '__main__':
try:
#初始化ros节点
rospy.init_node("object_tracker")
rospy.loginfo("object_tracking...")
image_converter()
rospy.spin()
except KeyboardInterrupt:
print("Shutting down object_tracker node.")
cv2.destroyAllWindows()
唔,代码注释写的比较全(自认为),有问题大家可以留言评论,我经常逛社区,看到的一定回复。然后贴个rostopic list给大家看一下~
大家也可以自己跑一下,算是一个很简单的Demo吧~
编程的话,主要还是Python + OpenCV,然后使用了ROS的通讯机制——话题通讯、ROS的功能包——cv_bridge和usb_cam、ROS的工具——image_view,当然这个过程肯定是没有离开ROS社区的帮助,毕竟调试不出bug的代码是很罕见的。
最后吧,我还是说一句话~
ROS = 通讯机制+工具箱+功能包+社区
ROS只是一个工具,它不是核心。你的思想、你的代码才是项目的核心,一定一定一定不要本末倒置。
初入门者,切记!
后续开发的话,我打算将被追踪目标的像素坐标以话题的形式的发布,通过PID+舵机实现一下锁定追踪的效果。也考虑使用水晶弹枪做个炮台,然后使用ROS的服务通讯机制,搞搞Python的多目标锁定追踪,炮台每瞄准一个打一个,就做下服务的反馈。唔,效果的话,就像是《喜羊羊与灰太狼之羊羊运动会》第58集——绝地大反击当中的,喜羊羊操控超大机械羊灭杀狼群的那种效果(可以从6分30秒的时候开始看20秒左右即可)。很nice!
慢慢搞吧,毕竟当代大学生最不缺的就是时间!
评论(3)
您还未登录,请登录后发表或查看评论