分类: Projet NAC

  • NAC-Python-GUI 和 NAC-React-App 的使用指南

    注意事项

    本页面为 使用 YOLOv4 检测目标并远程查看数据 的基本使用指南(并非开发指南,开发指南详见 https://xd.sh.cn/pje-nac/),可以根据本教程的步骤检测有鱼🐟类目标的视频或者摄像头所拍摄的实时录像。

    本项目至此暂停更新🤣,相关文章的列表如下所示:

    快速开始

    下载 nac-python-gui 的源码,目前有以下两个地址可以下载:

    下载权重文件,权重文件有两种物体的训练结果,分别为鱼🐟和仓鼠🐹(下方以鱼类为目标):

    下载和安装 Anaconda :

    Windows 的左下角直接搜索并打开 Anaconda Prompt,安装 openCV-python 和 PySimpleGUI,就是下方的命令复制粘贴回车即可。若系统内没有安装 imutils,也需要安装:

    pip install opencv-python
    pip install pysimplegui
    pip install imutils

    Windows 的左下角直接搜索并打开 Anaconda Navigator,选择并打开 Spyder,打开刚才克隆的 nac-python-gui 文件夹下的 yolo_video_with_webcam.py。正常情况下,此时其已经可以正常运行。

    如果有自备的视频(有鱼🐟类目标)可以将视频放在 nac-python-gui/videos 路径下,并且更改相应的文件名称。

    注意:视频分辨率不宜过大,过大可能导致 GUI 界面显示不全。

    若不想使用自备的视频可以直接使用摄像头进行检测,勾选上方 GUI 界面中的 Utiliser la webcam 可以进入摄像头模式。下图中展示了当使用摄像头实时识别的画面:

    注意:当关闭 GUI 界面时,摄像头可能不会被关闭。此时关闭整个 Spyder 软件即可完全退出并关闭摄像头。

    配合 nac-react-app 使用

    如果要配合 nac-react-app 使用,需要在 Anaconda Prompt 安装:

    pip install pyrebase4

    打开刚才克隆的 nac-python-gui 文件夹下的 yolo_video_with_webcam_focus.py,界面如下所示,即可登录到 nac-react-app 的账号。

    注意:该账号需要从 xd.sh.cn/nac/espace 注册,其会以加密的形式保存在 Google Firebase 上。管理员能够得知您的邮箱地址,但是无法获知您的密码。该账号和谷歌账号无关。

    注意:xd.sh.cn/nac 的路由仍存在一定问题,慢慢改,但可以直接访问具体链接。

    若成功登录该账号,则 nac-python-gui 所检测的画面(截图)会每隔一定的时间上传至 Firebase,并可以通过 xd.sh.cn/nac/espace 远程查看。

    进一步 DIY

    若需要进一步 DIY,可以直接修改 nac-python-gui 的相关代码,亦可通过 https://xd.sh.cn/pje-nac/ 参见详细的开发指南。若需要修改 nac-react-app 的代码,可以从下列的地址获得该网站的源码,该网站使用 React 开发:

    下载 nodejs 安装包:

    使用 Visual Studio Code 打开刚才克隆的 nac-react-app 文件夹。输入命令 npm install 下载必要的依赖包,下载完毕之后输入 npm start 即可预览。其将会自动打开浏览器并跳转至 http://localhost:3000/ 页面。

  • 使用 YOLOv4 检测目标并远程查看数据

    这个项目的主要目标是检测新型宠物(法文:Nouveaux animaux de compagnie。这里其实可以替换成各种物体)的状态并远程查看相关数据。虽然只有5个学分且专业不相关,但也挺好,总比体验极差的其他课(有限元)好玩一点。

    可以通过下方链接访问线上版本的 NAC Web App,按照页面中的说明进行操作(只有法语版本):

    项目目标

    项目使用目标检测算法(YOLOv4)训练经过标注之后的 NAC 数据集,使用 Google Colab 进行训练。使用 Python 和 OpenCV 结合 YOLOv4 检测 NAC 的活动状态,使用了 Google Firebase 进行数据的储存,用户可以通过 Web App(使用 React.js 制作)获取 NAC 活动状态的数据和画面。

    使用指南

    开发指南

    视频教程

    文章教程

    相关链接

    可以通过下方链接访问线上版本的 NAC Web App,按照页面中的说明进行操作(只有法语版本):

    开发者可以通过下列网址寻找到本项目的源代码:

  • Firebase 的数据传输和用户验证:以 Python 和 JavaScript 为例

    开始

    需要准备的一些工具:

    Python 和 Firebase 的数据传输

    要使 Python 和 Firebase 之间传输数据,需要安装额外的包,但是这些都是非官方维护的包,所以基本上都已经无法使用,但是这里提供一个经过改进的SDK包仍然可以使用:https://github.com/nhorvath/Pyrebase4。注意:官网提供的SDK包是给 Admin 管理员批量管理数据和账号使用的,也就是说其不适用于客户端。

    pip install pyrebase4

    登录 Google Firebase 创建一个新应用,创建后在下列路径中找到 Firebase 的配置文件:设置 –> 项目设置 –> 您的应用 –> Firebase SDK snippet –> 配置,复制类似下方的代码:

    // For Firebase JS SDK v7.20.0 and later, measurementId is optional
    const firebaseConfig = {
      apiKey: "QWERT",
      authDomain: "QWERT.firebaseapp.com",
      databaseURL: "https://QWERT.firebaseio.com",
      projectId: "QWERT",
      storageBucket: "QWERT.appspot.com",
      messagingSenderId: "00000000",
      appId: "QWERT",
      measurementId: "QWERT"
    };

    下方代码仅列出了重要的部分,其余部分可以转至 Github 查看源码。首先,需要引入相关的包:

    import pyrebase

    用户验证 Auth

    初始化 Firebase,调用用户验证模块:

    firebase = pyrebase.initialize_app(firebaseConfig) # 初始化数据
    pickle.dump(firebase,open('firebase_info.txt','wb')) # 将初始化后的数据保存,以便其他函数引用
    auth = firebase.auth()

    这里仅使用用户的邮箱和设置的密码进行验证操作,如果验证成功则会显示成功的弹窗。将验证过后的 Token 保存在本地,以便其他函数调用。由于 Firebase 不支持 @ 等特殊符号,如果需要保存这类特殊符号,则需要进行更改(按个人喜好)。

    user = auth.sign_in_with_email_and_password(UserEmail, UserPassword)
    sg.popup('登录成功!欢迎', UserEmail)
    pickle.dump(user,open('user_info.txt','wb'))
    userUniqueId = UserEmail.replace("@","__").replace(".","_")
    pickle.dump(userUniqueId,open('user_id.txt','wb'))  

    上传数据 Realtime database

    导入用户登录数据和凭证(注意:目前该凭证有效期为一个小时,但是可以进行续期):

    # Lire des données 读取数据
    firebase = pickle.load(open('firebase_info.txt','rb'))
    user = pickle.load(open('user_info.txt','rb'))

    获取对数据库(Realtime database)、云储存(Storage)服务的引用:

    # Obtenir une référence au service de base de données
    db = firebase.database()
    storage = firebase.storage()

    将要上传的数据类型为 json 形式,json文件的设计形式可以查看这篇文章

    dataUser = {
        "setup":{"time_update":300},
        "zone_name":["manger","libre","manger","libre",
                     "manger","libre","manger","libre"],
    }

    将 dataUser 上传至 user/userUniqueId 这个节点之下,userUniqueId 为用户特定的 Id 编号(自行设定),而后面的 user['idToken'] 为用户的身份验证(如果没有,将无法上传成功)。上传完成之后,如下图所示:

    db.child("users").child(userUniqueId).set(dataUser, user['idToken'])

    上传图片等文件 Storage

    上传图片等文件至 Firebase Storage ,操作类似于前者。下方案例中,本地文件储存在 image_raw/ 文件夹之下,且是以时间戳命名。使用 put 即可将文件上传至 userUniqueId/路径之下,userUniqueId 为用户特定的 Id 编号(自行设定),需要注意的是 child 处无需写文件后缀,不然无法上传成功。

    storage.child(userUniqueId + "/" + str(timeStamp)).put('image_raw/'+ str(timeStamp) + '.jpg', user['idToken'])

    参考资料

    JavaScript 和 Firebase 的数据传输

    在 JavaScript 项目中添加 Firebase

    添加 Firebase SDK 并初始化 Firebase,在应用的文件夹下安装 Firebase SDK:

    npm install --save firebase

    在应用中初始化 Firebase。在本 App 中,Firebase 的初始化文件在 srcComponentsfirebase.js 文件中,然后 App.js 文件调用一次即可。

    `srcComponentsfirebase.js` 
    import firebase from "firebase/app";
    require('firebase/analytics')
    const EnvironmentFirebase = () => {
      var firebaseConfig = {
        apiKey: "",
        authDomain: "fir-rtc-aff50.firebaseapp.com",
        projectId: "fir-rtc-aff50",
        storageBucket: "fir-rtc-aff50.appspot.com",
        messagingSenderId: "",
        appId: "",
        measurementId: "",
      };
      // Initialize Firebase
      firebase.initializeApp(firebaseConfig);
      firebase.analytics();
    };
    export default EnvironmentFirebase;

    用户注册和身份验证 Auth

    引入 firebase 和 auth 包:

    import firebase from "firebase/app";
    require("firebase/auth");

    创建一个组件,在本例中,该组件为 AuthInput.js,下方有两个函数分别为用户注册和用户登录。下方为一个注册按钮组件的例子,这个按钮将调用 signUpWithEmailPassword 函数,并将用户输入的用户名和密码传至该函数:

    <Button
        variant="contained"
        className={classes.buttonArea}
        onClick={() => {
            var email = document.getElementById("standard-email").value;
            var password = document.getElementById("standard-password")
            .value;
            signUpWithEmailPassword(email, password);
        }}
        >
        CRÉER UN COMPTE
    </Button>

    上方调用了 signUpWithEmailPassword 函数,在该函数中使用了 createUserWithEmailAndPassword 用户创建用户。此外,signInWithEmailAndPassword的操作也类似,具体的调用过程如下所示或者可以查询官方文档。若登录发生错误(用户名、密码不正确或者是用户已注册等情况),Firebase 将会返回不同的错误信息,这里为了简单起见,统一显示为“遇到了一些错误!”。

    // 注册用户
      // 文档在此:https://firebase.google.com/docs/auth/web/start?authuser=0
      function signUpWithEmailPassword(email, password) {
        firebase
          .auth()
          .createUserWithEmailAndPassword(email, password)
          .then((user) => {
            // Signed in
            console.log(user);
            alert("大概注册成功了!");
            storeSuccessedData(true);
          })
          .catch((error) => {
            var errorCode = error.code;
            var errorMessage = error.message;
            console.log(errorCode);
            alert("遇到了一些错误!");
          });
      }
    
      // 登录
      function signInWithEmailPassword(email, password) {
        firebase
          .auth()
          .signInWithEmailAndPassword(email, password)
          .then((user) => {
            // Signed in
            console.log(user);
            alert("大概登录成功了!");
            storeSuccessedData(true);
          })
          .catch((error) => {
            var errorCode = error.code;
            var errorMessage = error.message;
            console.log(errorCode);
            alert("遇到了一些错误!");
          });
      }

    读取数据和文件

    引入 firebase 和 auth 、database 、 storage 包:

    import firebase from "firebase/app";
    require("firebase/auth");
    require("firebase/database");
    require("firebase/storage");

    读取用户的数据,然后进行进一步处理。

    function readUserData(userEmail) {
      var adaRef = firebase
        .database()
        .ref("/cibles/" + userNameGenerate(userEmail))
        .once("value")
        .then( // Do something);
    }

    读取用户 Storage 中指定路径下的文件,userNameGenerate(userEmail) 为用户的个性路径,.list({ maxResults: 5 }) 表示读取最多5个文件(注意此处文件将会以升序的方式列出,也就是说列出的文件为最初的那些文件,而不是最新的文件。所以,这里我使用了listAll,然后使用 reverse() 将数组反转)。其余操作可以查看官方文档进行进一步查看。

    function readUserImg(userEmail) {
      // Create a storage reference from our storage service
      var storageRef = firebase
        .storage()
        .ref("/" + userNameGenerate(userEmail) + "/")
        .list({ maxResults: 5 })
        .then();
      return storageRef;
    }

    参考资料

  • 更简单一点!PySimpleGUI 快速创建 YOLO 目标检测的界面

    本文中的所用到的代码和文件可以从下列地址获取:


    使用 GUI (图形用户界面)的优点是用户应该能够立即使用应用程序,并能快速找到和使用他们想要的功能。在日常生活、工作中使用到的应用程序(浏览器、office、微信等等)就是 GUI 的体现,它使我们的工作变得效率更高、门槛更低。

    之前在桌面端只试过使用 Matlab 制作 GUI,具体可以查看这篇文章:Matlab GUI app designer 多人消费付款记账催债系统 。使用 Matlab 的 APP designer 或者 Guide (已过时)可以非常方便的制作出界面,因为只要拖拽即可。但是使用Matlab制作界面对于应用的使用者来说非常不便:它打包后的体积非常大,而且需要安装额外的 Matlab 运行库才能够使用。

    Python 作为目前非常“流行”的编程语言,实际上确实非常好用。对于我来说,它能够把日常琐碎重复的工作自动化完成,比如说文件重命名、批量替换等。制作图形界面能够让使用者更加方便的使用这些功能,而不是面对漆黑一片的代码行。

    在网上看到这么一句话:PySimpleGUI 是适用于人类使用的 Python GUI。它将 tkinterpyQtWxPython 等 Python 知名的 GUI 库转换为可移植的、用户更友好的界面开发库。当然缺点也是显而易见的,PySimpleGUI 只能制作出极为基础的界面,可能无法实现复杂且好看的界面设计。

    创建的步骤

    界面部分

    firebase_login

    下文以最简单的 firebase_login 界面为例:

    • 建议安装 Anaconda 管理库文件、使用 Python。
    • 使用 Anaconda Prompt 安装 PySimpleGUI,如果安装过程中提示需要安装其他的安装包,安装即可 :
    pip install PySimpleGUI
    • 引入文件:
    import PySimpleGUI as sg
    • 创建一个 PySimpleGUI 界面。该界面中有两个需要提交的变量:一个是用户邮箱、还有一个是用户密码,点击 OK 按钮之后即可执行验证 try。该界面中有以下几个元素,一个标题和图片、两个输入框、两个按钮:
      • sg.Text 创建文字,可以设置字体、大小、颜色和位置等。
      • sg.Image 引入图像,若其需要和上述的文字排列在同一行,则需要写在同一数组中。
      • sg.OKsg.Cancel 这是两个固定了文字的按钮,当然也可以自定义创建按钮,当点击事件(event)为 Cancel 或者用户点击关闭时,将退出应用;反之,将执行登录的步骤:
    sg.ChangeLookAndFeel('Reddit')
    layout = 	[
        [sg.Text('Connectez-vous à votre compte', size=(30,1), font=('Helvetica',12),text_color='#1c86ee' ,justification='left'),\
         sg.Image(r'images\nac-logo.png',key = "_WEATHER_IMG_",size=(100, 50))],
        [sg.Text('Email'), sg.In(size=(40,1), key='_USER_EMAIL_')],
        [sg.Text('Password'), sg.In(size=(40,1), key='_USER_PASSWORD_')],
        [sg.OK(), sg.Cancel()]
    ]
    win = sg.Window('Test vidéo pour YOLOv4 - NAC',
                    default_element_size=(21,1),
                    text_justification='left',
                    auto_size_text=False).Layout(layout)
    event, values = win.Read()
    if event is None or event =='Cancel':
        sys.exit()
    
    UserEmail = values['_USER_EMAIL_']
    UserPassword = values['_USER_PASSWORD_']
    
    auth = firebase.auth()
    
    try:
        # Connexion 登录
        user = auth.sign_in_with_email_and_password(UserEmail, UserPassword)
        sg.popup('登录成功!欢迎', UserEmail)
        pickle.dump(user,open('user_info.txt','wb'))
        userUniqueId = UserEmail.replace("@","__").replace(".","_")
        pickle.dump(userUniqueId,open('user_id.txt','wb'))        
    except:
        sg.popup('发生了一些错误,可能是用户名/密码错误!')
    win.Close()
    

    yolo_video_with_webcam

    下文为主功能界面的部分主要代码,其功能性的代码详见另一篇文章。

    • sg.In 输入框
    • sg.Slider 滑块,可以定义数值范围
    • sg.Button 和上文中类似,但是新定义了一个按钮;点击之后会执行
    • sg.FileBrowse 浏览文件按钮
    • sg.FolderBrowse 浏览文件夹按钮
    i_vid = r'videos\003_x264.mp4'
    o_vid = r'output\car_chase_01_out.mp4'
    y_path = r'yolo-coco'
    sg.ChangeLookAndFeel('Reddit')
    layout = 	[
    		[sg.Text('Test vidéo pour YOLOv4 - NAC', size=(28,1), font=('Helvetica',18),text_color='#1c86ee' ,justification='left'),\ # 换行
                 sg.Image(r'images\nac-logo.png',size=(100, 50))],
    		[sg.Text('Chemin de la vidéo'), sg.In(i_vid,size=(40,1), key='input'), sg.FileBrowse()],
    		[sg.Text('Chemin de la Yolo'), sg.In(y_path,size=(40,1), key='yolo'), sg.FolderBrowse()],
    		[sg.Text('Confiance'), sg.Slider(range=(0,1),orientation='h', resolution=.1, default_value=.5, size=(15,15), key='confidence')],
    		[sg.Text('Seuil'), sg.Slider(range=(0,1), orientation='h', resolution=.1, default_value=.3, size=(15,15), key='threshold')],
    		[sg.Text(' '*8), sg.Checkbox('Utiliser la webcam', key='_WEBCAM_')],
    		[sg.Button('Connecxion avec votre compte'),sg.OK(), sg.Cancel()]
    			]
    
    win = sg.Window('Test vidéo pour YOLOv4 - NAC',
    				default_element_size=(21,1),
    				text_justification='left',
    				auto_size_text=False).Layout(layout)
    event, values = win.Read()
    if event is None or event =='Cancel':
    	exit()
    if event == 'Connecxion avec votre compte':
     	firebase_login.firebaseLogin()
    use_webcam = values['_WEBCAM_']
    args = values
    
    win.Close()
    

    后一个界面分为左边栏和右边栏:

    • position_elem = win.FindElement('_POSITION_') :寻找界面中 key 为_POSITION_的元素。
    • position_elem.Update(targetPosition):将上面寻找到的元素更新为一个新的值。
    • event, values = win.Read(timeout=0):获取GUI中所有数据和变量。
    • gui_confidence = values['confidence']:获取GUI中的变量。
    • firebase_login.firebaseUploadData(targetPositionObject,timeRightNow):调用firebase_login文件中的firebaseUploadData的函数。
    	if not win_started: # if win_started is not None
    		win_started = True
    		sg.SetOptions(text_justification='Center') 
            # 左边栏
    		left_col =  [#界面元素#]
            # 右边栏
    		right_col = [#界面元素#]
    		layout = [
    			[sg.Column(left_col, element_justification='c'), sg.VSeperator(),
    			sg.Column(right_col, element_justification='c')]
    		]
            
    		win = sg.Window('YOLO Output',
    						default_element_size=(14, 1),
    						text_justification='left',
    						auto_size_text=False).Layout(layout).Finalize()
    		position_elem = win.FindElement('_POSITION_')
            #其他需要更新的元素#
    	else:
    		position_elem.Update(targetPosition)
    		#其他需要更新的元素#
    
    	event, values = win.Read(timeout=0)
     
    	if event is None or event == 'Exit':
    		break
    	gui_confidence = values['confidence']
    
    	loopTimes=loopTimes+1
    	if loopTimes % loopInterval == 0:
    		timeRightNow = round(time.time());
    		cv2.imwrite('image_raw/'+ str(timeRightNow) + '.jpg',frame) #存储为图像
    		firebase_login.firebaseUploadData(targetPositionObject,timeRightNow)
    

    检测部分

    检测使用了 OpenCV-Python结合 YOLOv4-darknet 训练得到的 weight 文件,检测部分完整的代码文件可以查看下列地址:0sheldonhuang0/nac-python-gui (github.com)coco.names内包含了需要检测的目标名称,给每个标签配上不同的颜色,以便区分。

    labelsPath = os.path.sep.join([args["yolo"], "coco.names"])
    LABELS = open(labelsPath).read().strip().split("n") #打开标签
    
    # 每个对象配备了不一样的颜色,以便在图片中标记时便于区分。
    np.random.seed(1)
    COLORS = np.random.randint(0, 255, size=(len(LABELS), 3),
        dtype="uint8")

    加载 YOLO weight 和 Config 文件:

    • 使用 cv2.dnn.readNetFromDarknet加载网络。
    • YOLO 含有很多的图层,getUnconnectedOutLayersNames() 用于提取输出图层的名称。
    # 加载 YOLO weight 和 Config 文件
    weightsPath = os.path.sep.join([args["yolo"], "custom-yolov4-detector_final.weights"])
    configPath = os.path.sep.join([args["yolo"], "custom-yolov4-detector.cfg"])
    
    # 加载 YOLO 文件
    print("[INFO] loading YOLO from disk...")
    net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
    ln = net.getLayerNames()
    ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
    
    # 初始化视频,输出视频的帧率和画面尺寸
    vs = cv2.VideoCapture(args["input"])
    writer = None
    (W, H) = (None, None)

    读取视频或者摄像头中下一帧的数据:

    # 读取视频或者摄像头中下一帧的数据
    if use_webcam:
        grabbed, frame = cap.read()
    else:
        grabbed, frame = vs.read()
        # 分辨率-宽度
        zone_width = int(vs.get(cv2.CAP_PROP_FRAME_WIDTH))/4
        # 分辨率-高度
        zone_height = int(vs.get(cv2.CAP_PROP_FRAME_HEIGHT))/2

    检测每一帧图片,frame 为视频截帧得到的图片:

        # 如果每帧图片尺寸为空,那抓取它
        if W is None or H is None:
            (H, W) = frame.shape[:2]
        # 从输入图像构造一个 blob,然后执行 YOLO 对象检测器的前向传递
        # 得出边界和概率
        blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416),
            swapRB=True, crop=False)
        net.setInput(blob)
        start = time.time()
        layerOutputs = net.forward(ln)
        end = time.time()

    初始化边界框、置信度、目标种类的数组,循环提取每个输出层,每一层中可能有多个框。提取当前目标的目标对象的种类和置信度,由于可以自行设置置信度的最低值,舍弃检测结果小于最低值的部分。最后得出方框的坐标和其他必要的信息,最后将显示在图片上。

       # 初始化边界框、置信度、目标种类的数组
        boxes = []
        confidences = []
        classIDs = []
    
        # 循环提取每个输出层
        for output in layerOutputs:
            # 循环提取每个框
            for detection in output:
                # 提取当前目标的类 ID 和置信度
                scores = detection[5:]
                classID = np.argmax(scores)
                confidence = scores[classID]
    
                # 通过确保检测概率大于最小概率来过滤较不精确的预测
                if confidence > gui_confidence:
                    # 将边界框坐标相对于图像的大小进行缩放,YOLO 返回的是边界框的中心(x, y)坐标,
                    # 后面是边界框的宽度和高度
                    box = detection[0:4] * np.array([W, H, W, H])
                    (centerX, centerY, width, height) = box.astype("int")
    
                    # 转换出边框左上角坐标
                    x = int(centerX - (width / 2))
                    y = int(centerY - (height / 2))
    
                    # 更新边界框坐标、置信度和种类 id 的列表
                    boxes.append([x, y, int(width), int(height)])
                    confidences.append(float(confidence))
                    classIDs.append(classID)
    
        # gui_confidence:置信度的阈值
        # gui_threshold:非最大抑制的阈值(调整容错率)
        idxs = cv2.dnn.NMSBoxes(boxes, confidences, gui_confidence, gui_threshold)
        targetPosition = []
        targetDetailNumber = []
        zone_info = []

    在下方代码后有一大段是为了便于将检测的数据转为可以上传至 Firebase 的 Json 数据,这里便不再赘述。至此,程序可以检测出所输入的视频或者摄像头录像中每一帧图片中所包含的目标物体和它们的位置,并且使用方框、名称和置信度标注出来。

    下一步将跳转至上一节中界面部分中的yolo_video_with_webcam界面,并且可以每隔一段时间将图像和目标数据上传至 Firebase 和本地。

    # 确定每个对象至少有一个框存在
    if len(idxs) > 0:
        # 循环画出保存的边框
        for i in idxs.flatten():
            # 提取坐标和宽度
            (x, y) = (boxes[i][0], boxes[i][1])
            (w, h) = (boxes[i][2], boxes[i][3])
    
            targetDetailNumber.append(classIDs[i])
            targetPosition.append([x+w/2,y+h/2]) # 每一帧的目标数量和位置
    
            # [[793.0, 517.0], [796.5, 423.0], [841.5, 367.0], [889.5, 499.5], 
            # [1001.5, 584.0], [254.5, 480.5], [204.0, 420.5], [693.5, 343.5], 
            # [73.0, 504.0], [123.5, 368.5], [752.0, 280.0], [1017.0, 508.5], 
            # [1232.0, 683.0], [14.5, 473.5], [398.0, 86.0], [1225.0, 374.5], 
            # [1097.0, 139.5], [1098.0, 600.5], [61.5, 172.5], [723.0, 140.0], 
            # [863.0, 174.0]]
            print(targetPosition)
            # [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0]
            print(targetDetailNumber)
    
            # 画出边框和标签
            color = [int(c) for c in COLORS[classIDs[i]]]
            cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
            text = "{}: {:.4f}".format(LABELS[classIDs[i]],
                                       confidences[i])
            cv2.putText(frame, text, (x, y - 5),
            cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)

    使用说明

    可以直接从 Github 上面下载本程序的源码,推荐使用 Anaconda 和 Spyder 进行编辑,并且需要主要安装以下一些包。若仍然缺少包,则仅需下载对应的包即可。

    • OpenCV-Python
    • Pyrebase4
    • PySimpleGUI
    • Numpy
    • imutils

    在安装完库之后,运行即可看到目标检测的实况。界面如上文的三张图所示,但可能和最终的界面有所不同。用户在正常情况下将执行 exe 文件启动桌面端软件。

    • 首个页面中上方两个输入框可以进行测试:如果用户有目标视频文件和对应的 weight 文件,那么可以从此处导入(注意导入文件和文件夹的路径是相对路径)。用户可以选择置信度和阈值,用户在点击 OK 之后即可进行预测。
    • 若用户的设备有摄像头,则可以勾选下方的使用摄像头选项,点击 OK 之后即可识别和检测摄像头所捕捉到的画面。
    • 用户在识别页面将只能调整检测的置信度和阈值,其他调整选项将放在在设置页面。上传的间隔时间,在目前版本无法调整,但在考虑范围之内。

    若用户有本系统的账号和密码,则可以通过左下角的按钮进行登录。如果登录成功,桌面端将会隔一段时间将用户所检测到的目标数据和图像上传至 Firebase。若用户没有登录, 则无法上传成功且在目前版本可能出错。

  • 目标检测用 YOLO:制作自己的数据集(观赏鱼)并使用 Darknet YOLOv4 在线训练

    注意:本文只是介绍了整个训练过程的大概流程和使用工具,不涉及详细的操作。训练的具体教程和内容可以浏览本文最底部链接里的 Jupyter Notebook 查看。

    做了件什么事情?

    目标检测是计算机视觉应用的一个方向,随着深度学习技术的发展,基于深度学习的目标检测开始变为主流,在无人驾驶、人工智能、人脸识别等领域大量使用。目标定位能够标出物体所在位置,目标分类能够判断在图像中是否含有该物体,而目标检测则是将两者相结合。

    因为有个课题的方向需要运用到目标检测这方面的技术,所以尝试了一下目前主流的目标检测技术。下方是一段b站养鱼up主的一段视频( 视频来源:零基础养鱼第二步——鱼要养得活 @ 我家有个动物园SUN)中的截图,图中的观赏鱼🐟都用框框标示了出来,在这些方框的上方都标有 poisson (法文:鱼)字样,旁边有介于0到1之间的数值代表了置信度。

    YOLOv4 算是目前比较强悍的一种目标检测算法,其全称是 You Only Look Once: Unified, Real-Time Object Detection,它在速度和精度都很有优势,Real-Time Object Detection 意味着它可以实时进行目标检测(检测摄像头或者是视频文件等)。

    下图中显示了不同目标检测算法准确度和速度之间的比较。可以看到 YOLOv4 在 MS COCO 数据集上获得了较高的 AP 值(average precision 平均精度),即在同FPS值(帧率)下有较高的AP值。

    MS COCO 是一种数据集,包含了80种物体分类、33万张图片、150万个目标对象。相比而言,本文使用到的数据集仅只有1种物体(观赏鱼)、122张图片(大多数来自b站萌宠区直播视频截图)、大约1000个不到目标对象(观赏鱼)。本文所使用的数据集(图片和标注数据)和训练结果(.weight文件,约250Mb)可以在本文最底部链接中下载

    上文图片来源:https://github.com/AlexeyAB/Darknet

    本篇文章主要内容如下图所示:首先使用 Labelimg 制作自己的数据集,然后白嫖 Google Colab (以下简称 Colab) 或 百度 Ai Studio (以下简称 Ai Studio)的服务器和显卡。在使用 Darknet 框架下,制作自己的数据集并训练自己的 YOLOv4 模型,得到 .weights 权重文件。

    使用了哪些工具?

    首先是使用到的软件,软件工具包括了以下这些(注意下列列表中的软件,仅针对使用 Ai Studio 或者是 Colab。若使用自己的电脑进行训练,则需要更复杂的步骤来配置环境。):

    在自己的电脑上处理数据

    1. Anaconda(用 Python 来处理一些重复的无意义操作,比如文件重命名、视频截帧。)

    2. Labelimg (用来进行物体目标标注。可以从 github 上直接获取,但是获取之后需要自行安装 Python 环境。)

    详见后文:制作自己的数据集。

    在 Ai Studio 或者 Colab 上训练

    1. Darknet YOLOv4 (用来进行训练,服务器上配置。可以从 github 上直接获取,但是获取之后不能直接用,需要自行编译。)

    若使用自己本地环境进行配置,则需要安装 Nvidia Cuda 和 CuDNN 以加快训练速度,还要有一块大显存的、高性能显卡。而如果使用 Ai Studio 和 Colab 进行训练则无需担心这些环境配置问题,也无需担心显卡性能问题,两者均配置了拥有 16GB 显存的英伟达特斯拉显卡

    我分别尝试了使用自己本子上的 Windows 和 Ubuntu 以及在线的机器学习环境 Ai Studio 和 Colab 进行编译和训练数据,其中 Windows 和 Ai Studio 在编译 Dartnet 这一关就卡住了(一直编译错误,可能和 Cuda 和 CuDNN 没装对有关)。

    而在 Ubuntu 上始终无法使用 CuDNN 进行训练,但是可以用单个 GPU 进行训练。使用我的破笔记本(Nivdia MX150(2018年轻薄本主流配置)),随便试了几张图(注意:只有几张图),结果不是“显卡内存不足”就是训练用时需要10个小时以上。

    后来分别使用了 Ai Studio 和 Colab 的分别进行数据训练。两者实际上没有优劣之分,只不过在使用 Ai Studio 的时候,在编译 Darknet 的时候始终遇到错误;而在使用 Colab 训练时间过长之后,会发生丢失文件的情况。Colab 提供的服务器、包括磁盘都是临时的,而不是永久的。所以在服务器上的任何改动,在关闭或者将服务器闲置时,上面所有的资料将会清空。在使用完这些服务器训练之后,要及时地将训练过后的权重文件以及配置文件下载下来。

    先试试别人的数据集

    在前文提到过数据集 MS COCO,在 Darknet 的 Github 的仓库下面(https://github.com/AlexeyAB/Darknet),作者提供了使用 MS COCO 数据集训练出来的权重文件可以作为测试使用。本文所使用的数据集(观赏鱼的图片和标注数据)和训练结果(.weight文件,约250Mb)可以在本文最底部链接中下载

    此外,这里还推荐一个数据集的分享、制作网站 Roboflow。虽然这上面的数据集也不是特别多,但是它可以用于小批量的测试,且每种数据集和标注数据都能下载成不同的格式以对应不同的目标检测算法。下方的截图所示为“是否戴口罩”的数据集,数据集内包含了戴口罩和不戴口罩的人,包含了149张原始图片和标注数据。点击 YOLO Darknet TXT 按钮即可下载适用于 YOLO Darknet 的数据集格式,既可以下载为 zip 压缩包,也可以直接使用它提供的链接在训练之前直接调用。

    https://app.roboflow.com/datasets

    制作自己的数据集

    第一步是获得一定数量含有目标的图片。经实测,当检测目标数不是很多的情况下,100到200张图片即可获得不错的检测效果。在上方提到的观赏鱼数据集中,一共仅包含了122张图片。这些观赏鱼图片中有一部分是来自搜索引擎直接搜索,另外一部分来自b站直播的视频截帧:下方链接只是其中的两个,另外的截图原视频来源由于主播未上播、当时也没有记录所以记不清了。

    这里可以使用 Python 脚本,截取视频中的截图,代码如下所示:(推荐使用 Anaconda 玩 Python,这样会更加容易管理环境。下方的代码需要在 Anaconda Prompt 中安装 opencv-python:pip install opencv-python

    ## 来源:python编程:使用opencv按一定间隔截取视频帧
    ## https://blog.csdn.net/xinxing__8185/article/details/48440133
    import cv2
    
    video_name = '006';
    vc = cv2.VideoCapture(video_name + '.mp4') #读入视频文件
    c=1
    
    timeF = 200  #视频帧计数间隔频率
    
    while rval:   #循环读取视频帧
        rval, frame = vc.read()
        print(rval,frame)
        if(c%timeF == 0): #每隔timeF帧进行存储操作
            cv2.imwrite('image_raw/'+'img'+ video_name + '_' + str(c) + '.jpg',frame) #存储为图像
        c = c + 1
        cv2.waitKey(1)
        print('输出1张图片')
    vc.release()

    接下来一步需要用到目标标注软件:Labelimghttps://github.com/tzutalin/labelImg)。这是一个用 python 写成的目标数据标注软件,用户可以用它来标注自己图片中的目标。使用方法也很方便,我使用了最简单的 Windows + Anaconda 的方案:首先傻瓜安装 Anaconda (Python 3 及以上),然后打开 Anaconda Prompt 安装 pyqt5 和 lxml:

    conda install pyqt=5
    conda install -c anaconda lxml

    将 Labelimg 克隆至本地或者下载 zip 压缩包到本地,用 Anaconda Prompt 打开 Labelimg 文件夹(路径根据自己的实际情况更改):

    cd C:UserssheldDownloadslabelImg-master
    pyrcc5 -o libs/resources.py resources.qrc
    python labelImg.py

    接着就会打开 Labelimg 的 GUI 界面,因为是图形操作界面,没有太多可说的,操作十分便捷:按键盘上的 w 键即可快速画框框。

    要注意的是:

    1. 如果需要使用 YOLO 作为数据输出格式,那么需要在软件的左侧进行设置。
    2. 每画完一张图需要保存,当然也可以使用它的“自动保存”模式。
    3. 如果所标注的对象只有一种,那么可以使用“单目标标注”模式,更加快捷。
    4. 每张图片将单独保存为一份 txt 文件,内容为所画方框的坐标,如下图左下角。(0 对应了检测目标列表的序号,在这里代表了 poisson。若有多种物体,则该序号则会不同。)

    使用 Ai Studio 或 Colab 进行训练

    在使用 Colab 的时候,整个操作均在一个 Jupyter Notebook(学 Python 必备神器)中执行,所以找到了一个实例 Jupyter Notebook(darknet 仓库下方有个官方的 Notebook),这个 Jupyter Notebook(.ipynb 文件)亦可在本文最下方的链接中下载。(注意: Colab 会因为一些众所周知的原因无法在国内登录。)

    在使用 Ai Studio 训练时一直遇到:当 Makefile 文件中 GPU = 1 时, Darknet 编译遇到错误,所以暂时放弃了使用 Ai Studio。然而,若不使用 GPU 进行训练,则环境可以仅使用 CPU 训练,但速度会严重降低。

    Jupiter Notebook 文件在 Ai Studio 中亦可使用,但 Ai Studio 没有对飞浆以外的算法进行适配,GPU环境可能不兼容。不过只要在使用 Ai Studio 时 Darknet 可编译成功,那么就可以按照官方步骤顺利进行后续的训练。

    这里不细说如何修改配置文件等细小的操作内容,因为整个操作过程均可以使用 Jupyter Notebook 完成,所有细节都能在这个.ipynb 文件中找到。这里仅说一下大概的流程:

    1. 查看 Cuda 版本号和显卡型号,下载、安装并配置对应的 CuDNN。

    2. 看看显卡的型号:Colab 是一块 16Gb 显存的英伟达 Tesla T4,Ai Studio 是一块 16Gb 显存的英伟达 Tesla V100。(百度用的显卡性能更强。)

    3. 根据显卡型号和 Cuda 版本安装 CuDNN,但是这步基本上略过,因为环境基本上都预先布置好了。

    4. 安装 Darknet

      • https://github.com/roboflow-ai/Darknet.git 克隆 Darknet 并解压。
      • 修改 Darknet 文件夹中的 Makefile 文件,把 GPU 和 CuDNN 都用起来!
      • 使用 make 命令编译 Darknet。
      • 下载 YOLOv4 ConvNet weights 文件并放入 Darknet 文件夹: yolov4.conv.137` 。
    5. 上传自己的数据集,并且解压在 Darknet 文件夹中。

    6. 新建并配置 obj.data 、train.txt、valid.txt 等文件。( Jupyter Notebook 中一段 Python 代码搞定 )

    7. 新建并配置 YOLOv4 的自定义文件 custom-yolov4-detector.cfg。( Jupyter Notebook 中一段 Python 代码搞定 )

    8. 使用 ./Darknet detector train data/obj.data cfg/custom-yolov4-detector.cfg yolov4.conv.137 -dont_show -map 就能训练啦!训练用时约10小时,每隔一段时间就会在 backup 文件夹下生成一个权重文件。

    9. 训练完成之后,最后一个权重文件为 custom-yolov4-detector_final.weights

    验证自己的训练结果

    1. 最简单的验证方法可以直接在训练完的 Darknet 文件夹内完成( Jupiter Notebook 中 Python 代码搞定)。
    2. 借用一个使用 PySimpleGUI 写的 Python 程序,叫做 [PySimpleGUI-YOLO]() https://github.com/PySimpleGUI/PySimpleGUI-YOLO,克隆或者下载下来之后解压缩。使用 Anaconda 安装 opencv-python 和 pysimplegui 包之后,即可运行。运行的效果就如本文开头所示。

    结语

    目标检测的旅程远还没有结束,本文只是介绍了整个训练过程的大概流程和使用工具,不涉及详细的操作和原理。训练的具体教程和内容可以浏览本文底部链接中的 Jupyter Notebook 查看。

    附件

    附件中包含了:

    • 观赏鱼 yolov4 训练后权重文件
      • custom-yolov4-detector.cfg
      • custom-yolov4-detector_best.weights
      • custom-yolov4-detector_final.weights
    • python 数据处理小脚本
    • 观赏鱼 yolov4 数据集.zip (1种物体(观赏鱼)、122张图片(大多数来自b站萌宠区直播视频截图)、大约1000个不到目标对象(观赏鱼)。)
    • yolov4.conv.137
    • YOLOv4-Darknet-Poisson-Jupter-NoteBook.ipynb

    链接地址: