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

简单的说一下链接的作用:

  1. 因为要用到截图的工具,snipaste

  2. 新电脑没有Python的环境,安装一下

  3. 以及就是我们的主角了,这个Python的库

  4. 要封装的命令

因为是在嵌入式的机器上面,所以在我们的本地机器上面布置没有意义

随便的布置一下就好了,这个就选择了最新的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 osimport os.pathimport runpyimport 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的这个库支持从以下几个地方要读取视频帧:

  1. CSI的摄像头

  2. RTSP的摄像头

  3. HTTP的摄像头,这里疯狂暗示Tello

  4. 以及我们的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

来进行捕捉的

继续看上面说的是什么,四个函数一起看了吧

  1. 是init的代码,在末尾调用了start()

  2. start()里面有实现了线程

  3. 线程里面又实现了是不是正确的读取

  4. 如果上一步正确,就开始读取

  5. 读取的时候又使用了try,except这样的结构

  6. 先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

然后将线程退出

如果卡住就会弹出运行时错误

代码读的不精细,也没有多少总结,有时间再看吧~

(0)

相关推荐