<jupyter_start><jupyter_text>JETANK 颜色追踪此文档用来实现一个颜色追踪功能，默认追踪亮黄色的物体。**注意：该文档涉及到舵机控制，在运行代码的过程中要小心舵机的运动范围内是否有易碎物品，并远离儿童。** 导入摄像头相关的函数库运行以下代码块后，稍等片刻，等待摄像头初始化，初始化成功后会在该代码块的下方出现一个300x300大小的摄像头实时视频画面。你可以在这个画面上点击右键，点击`Create New View for Output`，这样就可以将摄像头画面放置在窗口的一遍，即使浏览到该文档的后面部分，你依然可以随时观看摄像头的画面，该方法也同样适用于其它组件。多次运行该代码块有一定概率会初始化失败，在`jetbot.Camera`中已经包含了该问题的解决方法，你只需要重新运行该Kernel即可，但是注意不要使用该选项卡上方的圆形箭头来重新运行，那种方法有一定概率会依然初始化失败。建议重新运行该Kernel的方法：在左侧`File Browser`中，前方有绿色圆点的`*.ipynb`文件上点击右键（前面有绿色的原点说明Kernel正在运行中），选择`Shut Down Kernel`，你会发现绿色的圆点消失了，然后关闭掉该选项卡再双击刚才被关掉的`*.ipynb`文件来重新运行kernel。再次运行以下代码块，摄像头就应该可以正常初始化了。<jupyter_code>import traitlets
import ipywidgets
from IPython.display import display
from jetbot import Camera, bgr8_to_jpeg

camera = Camera.instance(width=300, height=300)

image_widget = ipywidgets.Image()  # this width and height doesn't necessarily have to match the camera

camera_link = traitlets.dlink((camera, 'value'), (image_widget, 'value'), transform=bgr8_to_jpeg)

display(image_widget)<jupyter_output><empty_output><jupyter_text>导入用来控制舵机的函数库要控制舵机，首先我们需要导入用来控制舵机的库，运行这句代码前请注意，由于Jetson Nano系统本身有服务已经占用了ttyTHS1(Jetson Nano与舵机通信的串口)，如果我们的安装脚本执行一切正常，会关闭掉之前占用ttyTHS1的服务，脚本也会自动添加赋予ttyTHS1串口0666的权限。如果之前的安装脚本执行失败，在这里使用ttyTHS1时可能会遇到Permission Denied的报错，如果遇到这种报错，以下是解决方法：- 点击JupyterLab左上角的加号，会打开一个新的Launcher选项卡，在此选项卡中的Terminal打开Jetson Nano的控制台。- 在控制台中输入以下内容后按回车    > sudo chmod 777 /dev/ttyTHS1- 输入Jetpack的默认密码`<PASSWORD>`，按回车确认修改权限。- 在左侧的`JETANK_1_servos.ipynb`上点击右键，点击`Shut Down Kernal`。- 关闭当前的`JETANK_1_servos.ipynb`选项卡后再重新双击左侧的`JETANK_1_servos.ipynb`运行新的Kernal。然后可以选择以下代码后按Ctrl+Enter来导入用于控制舵机的库，导入成功后会有如下提示：Succeeded to open the portSucceeded to change the baudrate运行以下代码块，导入舵机控制的函数库并定义舵机运动相关的函数。<jupyter_code>from SCSCtrl import TTLServo
import time

# 总线舵机过于频繁通信，有一定概率会导致rx、tx通信报错。
# 这里定义了每次通信后延迟一段时间，避免通信频率过高。
servoCtrlTime = 0.001

# 将1号舵机和5号舵机转动到中位。
# 1号舵机控制摄像头云台的PAN轴运动，水平方向的左右转动。
# 5号舵机控制摄像头云台的TILT轴运动，数值方向的上下俯仰。
TTLServo.servoAngleCtrl(1, 0, 1, 150)
time.sleep(servoCtrlTime)
TTLServo.servoAngleCtrl(5, 0, 1, 150)
time.sleep(servoCtrlTime)

# 摄像头向上运动。
def cameraUp(speedInput):
    TTLServo.servoAngleCtrl(5, -70, 1, speedInput)
    time.sleep(servoCtrlTime)

# 摄像头向下运动。
def cameraDown(speedInput):
    TTLServo.servoAngleCtrl(5, 25, 1, speedInput)
    time.sleep(servoCtrlTime)

# 摄像头向右转
def ptRight(speedInput):
    TTLServo.servoAngleCtrl(1, 80, 1, speedInput)
    time.sleep(servoCtrlTime)

# 摄像头向左转
def ptLeft(speedInput):
    TTLServo.servoAngleCtrl(1, -80, 1, speedInput)
    time.sleep(servoCtrlTime)

# 摄像头俯仰运动停止。
def tiltStop():
    TTLServo.servoStop(5)
    time.sleep(servoCtrlTime)

# 摄像头左右运动停止。
def panStop():
    TTLServo.servoStop(1)
    time.sleep(servoCtrlTime)

#运行以上代码块后，摄像头云台的1号和5号舵机会缓慢转动到中位。<jupyter_output>Succeeded to open the port
Succeeded to change the baudrate<jupyter_text>颜色识别并追踪的功能在上一章节的`ColorSelect 颜色选取`章节中我们介绍了如何获得某一物体色彩的HSV数值，并记录下了数值的最大值和最小值，你可以将那个最大值和最小值赋予`colorUpper`和`colorLower`这两个参数，注意格式：`np.array([H, S, V])`。如果你不替换为你自己的物体颜色，也可以使用默认的程序来实现颜色识别，默认追踪的颜色时亮黄色物体。<jupyter_code>import cv2
import numpy as np

# 定义需要识别的颜色。

# 黄色 #FFFF00
colorUpper = np.array([44, 255, 255])
colorLower = np.array([24, 100, 100])

# 红色 FF0000
# colorUpper = np.array([180, 255, 255])
# colorLower = np.array([160, 100, 100])

# 绿色 #00FF00
# colorUpper = np.array([50, 255, 255])
# colorLower = np.array([70, 200, 100])

# 蓝色 #0000FF
# colorUpper = np.array([110, 225, 255])
# colorLower = np.array([135, 180, 200])

# 青色 #00FFFF
# colorUpper = np.array([80, 255, 255])
# colorLower = np.array([105, 180, 180])

# 洋红 #FF00FF
# colorUpper = np.array([140, 255, 255])
# colorLower = np.array([160, 150, 200])

# 定义摄像头转向这一物体时的位置宽容度。
# 这一数值越高，摄像头瞄准时的精度越高，但过高的数值也可能会导致摄像头连续不断地左右摆动。
error_tor = 25

# 这是简易PID调节器的P值，是运动速度的比例调节系数。
# 这个数值过高会导致摄像头转动运动超调，过低会导致颜色追踪的反应速度过慢。
PID_P = 3

# 颜色识别并追踪的函数。
def findColor(imageInput):
    # 将视频帧转换为HSV色彩空间。
    hsv = cv2.cvtColor(imageInput, cv2.COLOR_BGR2HSV)
    
    # 为符合目标颜色的像素建立遮罩。
    mask = cv2.inRange(hsv, colorLower, colorUpper)
    
    # 腐蚀，这一过程会将刚才选取出来的遮罩中，比较小的区域去除，可以理解为去噪点。
    mask = cv2.erode(mask, None, iterations=2)
    
    # 膨胀，刚才的腐蚀过程会导致大的区域变小，小的区域变没，这一步是为了将大的区域恢复为之前的大小。
    mask = cv2.dilate(mask, None, iterations=2)
    
    # 获取符合的区域轮廓。
    cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE)[-2]
    center = None
    
    # 如果发现存在有符合的区域，则开始控制舵机运动，实现色彩追踪。
    if len(cnts) > 0:
        # 绘制文字，说明目标已发现。
        imageInput = cv2.putText(imageInput,'Target Detected',(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
        
        # 找到最大的那个区域的轮廓。
        c = max(cnts, key=cv2.contourArea)
        
        # 获取这个区域的中心点位置以及这一区域的半径。
        ((box_x, box_y), radius) = cv2.minEnclosingCircle(c)
        M = cv2.moments(c)
        center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
        
        # X、Y是区域的中心点位置。
        X = int(box_x)
        Y = int(box_y)
        
        # error_X、error_Y是区域中心点位置与画面中心点位置的误差绝对值。
        error_Y = abs(150 - Y)
        error_X = abs(150 - X)
        
        # 绘制出这个区域的大小和位置。
        cv2.rectangle(imageInput,(int(box_x-radius),int(box_y+radius)),(int(box_x+radius),int(box_y-radius)),(255,255,255),1)
        
        if Y < 150 - error_tor:
            # 控制摄像头向上转动。
            imageInput = cv2.putText(imageInput,'Looking Up',(10,50), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
            cameraUp(error_Y*PID_P)
        elif Y > 150 + error_tor:
            # 控制摄像头向下转动。
            imageInput = cv2.putText(imageInput,'Looking Down',(10,50), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
            cameraDown(error_Y*PID_P)
        else:
            # 竖直方向上的误差小于宽容度，摄像头俯仰方向停止运动。
            imageInput = cv2.putText(imageInput,'Y Axis Locked',(10,50), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
            tiltStop()

        if X < 150 - error_tor:
            # 控制摄像头向左转动。
            imageInput = cv2.putText(imageInput,'Looking Left',(10,80), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
            ptLeft(error_X*PID_P)
        elif X > 150 + error_tor:
            # 控制摄像头向右转动。
            imageInput = cv2.putText(imageInput,'Looking Right',(10,80), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
            ptRight(error_X*PID_P)
        else:
            # 水平方向上的误差小于宽容度，摄像头水平方向停止运动。
            imageInput = cv2.putText(imageInput,'X Axis Locked',(10,80), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
            panStop()

    # 如果没有发现符合目标颜色的区域，摄像头停止转动。
    else:
        imageInput = cv2.putText(imageInput,'Target Detecting',(10,20), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(255,255,255),1,cv2.LINE_AA)
        tiltStop()
        panStop()
    
    return imageInput<jupyter_output><empty_output><jupyter_text>注意，运行以上代码块后你所看到的画面并不会发生改变，当方法函数定义完成，最后改变`image_widget.value`的值并调用`camera.observe()`函数才会在实时画面中观察到最终的效果，前者用于显示处理过的画面，后者用于当采集到新的画面后立即调用图像处理的相关方法，所以在最后一个代码块运行前，你在实时视频的画面上看不到变化是正常的。运行以下代码块，开启颜色识别并追踪功能。**注意：这运行以下代码块后舵机可能会转动，注意舵机的运动范围内是否有易碎物品，并远离儿童。**<jupyter_code>def execute(change):
    global image_widget
    image = change['new']
    image_widget.value = bgr8_to_jpeg(findColor(image))
    
execute({'new': camera.value})
camera.unobserve_all()
camera.observe(execute, names='value')<jupyter_output><empty_output><jupyter_text>关闭图像处理和摄像头运行以下代码块来关闭该图像处理功能，并使舵机停止运动。<jupyter_code>camera.unobserve(execute, names='value')

time.sleep(1)

tiltStop()
panStop()<jupyter_output><empty_output><jupyter_text>在结束该例程前，我们需要关闭摄像头，这样才可以释放摄像头资源供其它例程使用。<jupyter_code>camera.stop()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12445673646779928, 'fraction_numerical': 0.05281180034242065, 'mean_word_length': 4.998420221169036, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '544235', 'license': 'MIT', 'n_tokens_mistral': 4577, 'n_tokens_neox': 4808, 'n_words': 377, 'path': 'JETANK_5_colorTracking/colorTracking_cn.ipynb', 'repo_name': 'waveshare/JETANK', 'size': '7589'}