jetson NanoCamera(USB摄像头连接)
来自于GitHub的一个开源的Python库,专门用于英伟达Jetson Nano的USB相机驱动。
先放一些要用到的库以及相关用到的链接的出处:
https://zh.snipaste.com/download.html
https://www.python.org/ftp/python/3.9.5/python-3.9.5-amd64.exe
https://codeload.github.com/thehapyone/NanoCamera/zip/refs/heads/master
https://developer.download.nvidia.cn/embedded/L4T/r32_Release_v1.0/Docs/Accelerated_GStreamer_User_Guide.pdf?uIzwdFeQNE8N-vV776ZCUUEbiJxYagieFEqUoYFM9XSf9tbslxWqFKnVHu8erbZZS20A7ADAIgmSQJvXZTb0LkuGl9GoD5HJz4263HcmYWZW0t2OeFSJKZOfuWZ-lF51Pva2DSDtu2QPs-junm7BhMB_9AMQRwExuDb5zIhf_o8PIbA4KKo
简单的说一下链接的作用:
因为要用到截图的工具,snipaste
新电脑没有Python的环境,安装一下
以及就是我们的主角了,这个Python的库
要封装的命令
因为是在嵌入式的机器上面,所以在我们的本地机器上面布置没有意义
随便的布置一下就好了,这个就选择了最新的3.9
pip一下,报错
C:\Users\109\AppData\Local\Programs\Python\Python39\Scripts
添加一下这个路径就好了
添加到这里
然后可以pip一下
可以把克隆的文件,大致的看一下结构
其实很短,主要的只有一个文件就是NanoCam这个实现的文件
上面就是一些例子了,先看一个
先对代码格式化,快捷键就好
但是没有装过库,扩展提示了要安装
下面是一个自动安装的脚本
全是绝对的路径,第二个路径有意思我们去看看
C:/Users/109/AppData/Local/Programs/Python/Python39/python.exe
c:\Users\109\Desktop\AAAAAAAA\VSCode\VSCode-win32-x64-1.56.2\data\extensions\ms-python.python-2021.5.842923320\pythonFiles\pyvsc-run-isolated.py
pip install -U autopep8 --user
这个路径
c:\Users\109\Desktop\AAAAAAAA\VSCode\VSCode-win32-x64-1.56.2\data\extensions\ms-python.python-2021.5.842923320\pythonFiles\
在这里
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
if __name__ != "__main__":
raise Exception("{} cannot be imported".format(__name__))
import os
import os.path
import runpy
import sys
def normalize(path):
return os.path.normcase(os.path.normpath(path))
# We "isolate" the script/module (sys.argv[1]) by removing current working
# directory or '' in sys.path and then sending the target on to runpy.
cwd = normalize(os.getcwd())
sys.path[:] = [p for p in sys.path if p != "" and normalize(p) != cwd]
del sys.argv[0]
module = sys.argv[0]
if module == "-c":
ns = {}
for code in sys.argv[1:]:
exec(code, ns, ns)
elif module.startswith("-"):
raise NotImplementedError(sys.argv)
elif module.endswith(".py"):
runpy.run_path(module, run_name="__main__")
else:
runpy.run_module(module, run_name="__main__", alter_sys=True)
直接找到,看看就好了。还是看我们的代码才是正事~
先看第一个代码
按照结构简单的划分了
camera = nano.Camera(camera_type=1, device_id=1,
width=640, height=480, fps=30)
个人觉得最重要的就是这个代码,对摄像头的初始化:
我们开始读,到这个里面寻找答案
这个库依赖于time,多线程,cv2
只有一个类,其实就是一个封装的变量库
这里是所有的方法,下划线是私有的方法
def __init__
(self,
camera_type=0,
device_id=0,
source="localhost:8080",
flip=0,
width=640,
height=480,
fps=30,
enforce_fps=False,
debug=False):
一开始这个初始化代码需要仔细的研究一下
我们先看支持的相机的种类
是不是很好看,哈哈哈哈
总结一下,nano的这个库支持从以下几个地方要读取视频帧:
CSI的摄像头
RTSP的摄像头
HTTP的摄像头,这里疯狂暗示Tello
以及我们的USB摄像头
摄像头的id没有什么说的,直接就是这的参数传进来的
其实接下来有两个参数都是一样的传参
# initialize all variables
self.fps = fps
self.camera_type = camera_type
self.camera_id = device_id
# for streaming camera only
self.camera_location = source
这个就是没有对应接受函数的几个参数
这个是不是看不清
def __csi_pipeline(self, sensor_id=0):
return ('nvarguscamerasrc sensor-id=%d ! '
'video/x-raw(memory:NVMM), '
'width=(int)%d, height=(int)%d, '
'format=(string)NV12, framerate=(fraction)%d/1 ! '
'nvvidconv flip-method=%d ! '
'video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! '
'videoconvert ! '
'video/x-raw, format=(string)BGR ! appsink' % (sensor_id,
self.width, self.height, self.fps, self.flip_method,
self.width, self.height))
还是不够好看,继续
'nvarguscamerasrc sensor-id=%d ! '
'video/x-raw(memory:NVMM), '
'width=(int)%d, height=(int)%d, '
'format=(string)NV12, framerate=(fraction)%d/1 ! '
'nvvidconv flip-method=%d ! '
'video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! '
'videoconvert ! '
'video/x-raw, format=(string)BGR ! appsink' % (sensor_id,
self.width, self.height, self.fps, self.flip_method,
self.width, self.height)
所有的魔法都是在这里实现,这个是一个底层的解码应用
我找到了一个PDF转WORD的网站
https://smallpdf.com/cn/result#r=fb65dd890902460585b78214673467d6&t=pdf-to-word
就用一下下,完全ok
就完美转换了
又找到一个WORD转HTML的
要钱,还注册麻烦
用自带的吧
然后就ok了,为什么转换多次。
。。。。我想翻译成中文的看而已
是不是真不错,我觉得也是真不错
我们找到第一个参数的作用了,调用了一个应用程序
传感器的id
捕捉时候的硬件参数,自己对照吧
我发现,一直enter是下一个
Tab一下会将焦点放在上一个寻找
队列中的最大内存量,字节为单位
对于具有低内存分配要求的解码用例(例如在Jetson Nano上),
请使用gstomx解码器插件的enable-low-outbuffer属性。
使用GSTREAMER-1.0进行视频格式转换
的NVIDIA专有nvvidconv的GStreamer-1.0插件允许转换OSS之间(原始)视频格式和NVIDIA视频格式。所述nvvidconv插件目前支持在此描述的格式转换部
raw-yuv输入格式
目前nvvidconv支持的I420,UYVY,YUY2,YVYU,NV12,BGRx,和RGBA原始YUV输入格式。
视频格式的转换和缩放
有参数查不到,但是感觉是一个翻转的代码
'video/x-raw,
width=(int)%d,
height=(int)%d, f
ormat=(string)BGRx ! '
https://github.com/opencv/opencv/issues/11059
关于以上格式的一些讨论
我对编码不熟悉,只能看看
def __usb_pipeline(self, device_name="/dev/video1"):
return ('v4l2src device=%s ! '
'video/x-raw, '
'width=(int)%d, height=(int)%d, '
'format=(string)YUY2, framerate=(fraction)%d/1 ! '
'videoconvert ! '
'video/x-raw, format=BGR ! '
'appsink' % (device_name, self.width, self.height, self.fps))
关于USB摄像头的捕捉代码
可以看到是/dev/video1这里捕捉的
可以看到对于USB的读取是,YUV的原生格式
然后会转换到下面几个常用的格式
https://blog.csdn.net/weixin_41944449/article/details/81805164
这篇文章就是比较好的一个解读这个插件的源码
感兴趣的可以去看看
时间有点久远,我们再回忆一下我们的代码:
camera = nano.Camera(camera_type=1, device_id=1,
width=640, height=480, fps=30)
相机的类型选择,代码在下面:
def open(self):
# open the camera inteface
# determine what type of camera to open
if self.camera_type == 0:
# then CSI camera
self.__open_csi()
elif self.camera_type == 2:
# rtsp camera
self.__open_rtsp()
elif self.camera_type == 3:
# http camera
self.__open_mjpeg()
else:
# it is USB camera
self.__open_usb()
return self
有点switch的意思
具体在使用的时候,相机的id不一定是1,需要自己去看设备的根节点:
可以通过在终端上运行:ls / dev / video *来查看已连接的USB摄像机
对于USB摄像机/ dev / video2,device_id将为2,注意切换。
再回顾一下关于我们实现里面的init代码:
def __init__(self, camera_type=0, device_id=0, source="localhost:8080", flip=0, width=640, height=480, fps=30,
enforce_fps=False, debug=False):
# initialize all variables
self.fps = fps
self.camera_type = camera_type
self.camera_id = device_id
# for streaming camera only
self.camera_location = source
self.flip_method = flip
self.width = width
self.height = height
self.enforce_fps = enforce_fps
self.debug_mode = debug
# track error value
'''
-1 = Unknown error
0 = No error
1 = Error: Could not initialize camera.
2 = Thread Error: Could not read image from camera
3 = Error: Could not read image from camera
4 = Error: Could not release camera
'''
# Need to keep an history of the error values
self.__error_value = [0]
# created a thread for enforcing FPS camera read and write
self.cam_thread = None
# holds the frame data
self.frame = None
# tracks if a CAM opened was succesful or not
self.__cam_opened = False
# create the OpenCV camera inteface
self.cap = None
# open the camera interface
self.open()
# enable a threaded read if enforce_fps is active
if self.enforce_fps:
self.start()
完整的代码
self.debug_mode = debug
# track error value
'''
-1 = Unknown error
0 = No error
1 = Error: Could not initialize camera.
2 = Thread Error: Could not read image from camera
3 = Error: Could not read image from camera
4 = Error: Could not release camera
'''
当你的debug开关开启时,以及要输入的等级
# Need to keep an history of the error values
self.__error_value = [0]
# created a thread for enforcing FPS camera read and write
self.cam_thread = None
这两个就很清晰了,就是实现了你程序里面的一些状态的保存
剩下就是线程的开关,因为视频流不是很简单的就可以使用
要捕获,解码,渲染,还有一些别的操作,得用线程实现
def start(self):
self.cam_thread = Thread(target=self.__thread_read)
self.cam_thread.daemon = True
self.cam_thread.start()
return self
在这里,可以看到是在开始这个函数进行了init
我以前的已经说得很详细了,这里不bb了
def __thread_read(self):
# uses thread to read
time.sleep(1.5)
while self.__cam_opened:
try:
self.frame = self.__read()
except Exception:
# update the error value parameter
self.__error_value.append(2)
self.__cam_opened = False
if self.debug_mode:
raise RuntimeError(
'Thread Error: Could not read image from camera')
break
# reset the thread object:
self.cam_thread = None
好家伙儿,看走眼了
这个线程在这里,还是眼花了
这个功能分为初始化,实现,恢复现场
给1.5s的时间来保存系统给资源来运行
接下来是try和except的搭配
首先看这个read的读取情况
你看这个东西,这个读取的标志就是cv2里面的标志
都学的连起来了
这个是一个标志位,注释写的很清楚了
CAM是不是正常的打开,其实这个地方写的有点鬼畜
追踪这个相机是不是成功的打开,应该是这样的翻译
# Tracks if camera is ready or not(maybe something went wrong)
def isReady(self):
return self.__cam_opened
这个代码加上,上面就不呼噜了,#跟踪相机是否准备就绪(可能出了点问题),有问题就会导致下面的东西不能正常的运行,其实也是在保证程序的正常运行。
def read(self):
# read the camera stream
try:
# check if debugging is activated
if self.debug_mode:
# check the error value
if self.__error_value[-1] != 0:
raise RuntimeError(
"An error as occurred. Error Value:", self.__error_value)
if self.enforce_fps:
# if threaded read is enabled, it is possible the thread hasn't run yet
if self.frame is not None:
return self.frame
else:
# we need to wait for the thread to be ready.
return self.__read()
else:
return self.__read()
except Exception as ee:
if self.debug_mode:
raise RuntimeError(ee.args)
读取使用的代码
先插一点,如果你成功的将初始化的参数传入
会调用下面的某一个就是你要捕捉的硬件,会使用cv2的VideoCapture
来进行捕捉的
继续看上面说的是什么,四个函数一起看了吧
是init的代码,在末尾调用了start()
start()里面有实现了线程
线程里面又实现了是不是正确的读取
如果上一步正确,就开始读取
读取的时候又使用了try,except这样的结构
先try里面判断debug的等级,里面会触发运行时错误
具体的这个有个函数我也没有找到定义,所以可能不是函数,但是看见了蛛丝马迹:
if self.enforce_fps:
self.cap = cv2.VideoCapture(self.__usb_pipeline_enforce_fps(
self.camera_name), cv2.CAP_GSTREAMER)
看,就是这里,cv2的接口从这个接口处获得数据
这里这样处理可能好看一些
就是在不停的判断各种成功读取的条件而已
也就是我们的两行代码,背后发生的故事~
这些代码都是常规的cv2代码,没有什么意思了
最后还有一个释放的代码
def release(self):
# destroy the opencv camera object
try:
# update the cam opened variable
self.__cam_opened = False
# ensure the camera thread stops running
if self.enforce_fps:
if self.cam_thread is not None:
self.cam_thread.join()
if self.cap is not None:
self.cap.release()
# update the cam opened variable
self.__cam_opened = False
except RuntimeError:
# update the error value parameter
self.__error_value.append(4)
if self.debug_mode:
raise RuntimeError('Error: Could not release camera')
这里保证各种标志位变为False
然后将线程退出
如果卡住就会弹出运行时错误
代码读的不精细,也没有多少总结,有时间再看吧~