Sipeed矽速科技
直播中

裸奔小耗子

6年用户 67经验值
擅长:嵌入式技术 处理器/DSP 控制/MCU
私信 关注
[经验]

【Longan Pi 3H 开发板试用连载体验】给ChatGPT装上眼睛,还可以语音对话

感谢发烧友论坛和Sipeed举办的本次活动,让我有机会可以体验到Longan Pi 3H这块超迷你的H618开发板。我打算用这块板子作为服务器,实现一个可以对话交互并且具备可视能力的ChatGPT助手。

我计划先在板子上上尝试部署CNN卷积神经网络,利用webcam获取实时图像,实现物体识别。同时利用麦克风实现语音输入与文字识别。当触发语音识别后,会同时进行语音识别与图像识别操作。识别完成后将结果整合,整合后再输入ChatGPT或其他大语言模型。最后再将大语言模型的输出结果利用TTS转化为语音进行播放。实现Chatgpt多模态输入(图像输入,语音输入),在赋予ChatGPT视觉的同时添加语音对话能力。

目前计划使用的具体方案和流程如下:

  1. 使用speech_recognition进行语音输入,完成后将语音数据导入Whisper或google等语音识别模型中进行语音识别。
  2. 检测到语音输入完成后,程序会从webcam的视频流中截取最新的一帧,并利用YOLO或是RetinaNet,FPN等神经网络模型进行图像识别。
  3. 图像识别的信息与语音识别都得到后进行整合,通过GPT 提示词训练,将所有信息整合成文本并输入ChatGPT。
  4. 将ChatGPT返回的回复利用Edge-TTS进行播放,完成与具备视觉能力的ChatGPT进行语音交互。
  5. 预期成果: 开源所有代码,分享实现的全过程,视频展示实现结果。

快递很快,没两天就收到了,先看看东西有哪些。

开箱

微信图片编辑_20240412123341.jpg

盒子很迷你,里面填充了满满的泡沫,板子保护的非常好。这个盒子大小适中,拿来做零件盒也是很好的。

打开盒子后,里面就是一块开发板,核心板和底板已经组装在一起了,不需要自己再另行组装。

微信图片_20240412123353.jpg

微信图片_20240412123357.jpg

看了下板子上内存和EMMC型号,发现是最高配的版本,4G内存,这个在刷好固件后使用btop --utf-force也可以看到。
image.png

当然了这是后话,那么现在我们就开始烧录固件。

固件烧录

这块板卡自带了EMMC,但同样支持从内存卡启动。因此最简单最快速的使用方式就是直接使用内存卡来运行系统。平时我自己在使用这一类板卡时也更倾向于使用内存卡或U盘这一类可移动设备来作为flash使用。虽然EMMC的速度和稳定性会更好,但我们知道flash是有擦写次数限制的,尤其是在使用SWAP分区的情况下,flash寿命会大大减少。因此如果使用外置储存,flash损坏后更换起来非常便捷;而更换EMMC就麻烦了很多。另外一点更重要的是,如果手上有多台设备,可以用一张卡玩遍所有设备,而不需要每一套设备都需要单独烧录一个系统,单独去更新软件包。

闲话说到这。我们先去官网上下载最新的Debian镜像,最新镜像地址在官方wiki上有:

https://wiki.sipeed.com/hardware/zh/longan/h618/lpi3h/3_images.html

注意镜像版本要使用SD版本的镜像。

接下来还需要烧录工具,我使用的是balenaEtcher便携版。虽然这个工具体积大一点,但使用起来非常无脑,一键烧录。而且便携版也不需要安装,还算比较方便。

接着我们找一张16G以上的内存卡,插入电脑,打开balenaEtcher,选择镜像和内存卡,点击烧录,接着等待完成即可。
select_device

刷完后,将内存卡插入开发板,就算完成所有准备工作。开发板的内存插槽比较隐蔽,在这个位置:
微信图片_20240412123411.jpg

由于我没有HDMI显示器和键盘,因此无法像官方WIKI那样使用桌面系统。因此首次开机我也需要用无头方式配置无线网络。具体操作我们下期在评论区中再见。

回帖(7)

裸奔小耗子

2024-4-15 14:44:09
本帖最后由 裸奔小耗子 于 2024-4-16 06:56 编辑

首次启动
由于我没有HDMI显示器,也没有键盘,因此无法像官方wiki那样正常使用桌面登录完成设置。好在我还有一根网线,因此我们可以无头模式来开始使用。
先从路由器上的lan口拉一根网线出来连接到开发板,接着开发板上电。接下来我们需要耐心等待,直到在路由器后台界面看到有新设备出现,Longan Pi上线。

我们可以看到Longan Pi已经出现在路由器的DHCP池中,但是WIFI设备中并没有,说明有线网已经正常工作。
使用ssh登录Longan Pi,在windows下打开一个cmd窗口,输入命令ssh sipeed@192.168.199.219。这里的ip就是我们从路由器得到的ip,出现输入密码的prompt后,输入licheepi,就可以登陆到开发板。

配置
登陆完成后,首先我们需要做的是连接好wifi,这样就不再受网线的束缚。这里我们使用一种相比官网wiki更简单的方式来配置网络,用Network Manager。由于系统中并没有自带,因此我们需要安装一下。
sudo apt install network-manager安装好后,使用sudo nmtui打开图形化配置界面,选择第二个连接网络,然后找到自己家的wifi,按回车连接,输入密码,就大功告成了。

连接好后使用sudo ifconfig命令来查看一下连接情况,可以看到wifi已经获取到了ip地址。

这时候我们可以返回到路由器,再来确认一下连接情况

可以看到现在DHCP租约中同时存在有线和无线两个ip,而下面的wifi客户列表中也出现了longan Pi,确认连接无误,我们可以放心大胆的拔掉网线了,只需要找个手机充电器,就可以放在家中任何一个角落,作为一个长期在线的小服务器来使用。但我在测试过程中发现,H618虽然在发热方面比H3有了非常大的进步,待机温度仅40多度,并不高(多年老司机用手估计的,看了下kernal中并没有temp设备,因此在系统中无法查看具体温度),但是一旦在执行任务时,例如apt upgrade等,可能就会由于高温而死机。因此我专门去买了一片散热器来搭配使用。买不到尺寸刚好的散热器,因此只能买宽度适合,但长度偏长的先凑合用。



1 举报

裸奔小耗子

2024-4-16 07:11:28
系统配置

在上一篇内容中大家实际操作的话可能会意识到一些问题,例如为什么ifconfig直接执行的话会显示找不到该命令,但是使用sudo ifconfig却可以正常工作;又例如说为什么每一次sudo命令都需要输入密码,非常麻烦。因此这里我们要修改一下系统配置,方便接下来的使用。
首先解决第一个问题,部分命令一定要在sudo下才能找到的问题。
Linux由于历史遗留问题,bin文件夹较为混乱,例如
  1. /bin
  2. /sbin
  3. /usr/bin
  4. /usr/sbin
  5. /usr/local/bin
  6. /usr/local/sbin
这些文件夹中都有可执行文件,而这些bin文件夹在大多数unix系统中都存在,有的系统还会有更多其他的bin文件夹。这些bin文件夹里的文件有些是一样的,有些是软连接,而有些不一样,非常混乱。历史遗留问题目前似乎并没有个达到共识的解决方案,因此操作系统的做法是把各种乱七八糟的bin文件夹都添加到path中,这样总能找到需要执行的文件。而Longan Pi的系统默认在使用sipeed用户登录时,并没有将各种sbin文件夹添加到PATH中,导致像ifconfig这种只存在于sbin文件夹内的可执行文件就无法执行。因此我们要做的只是添加相应的PATH,这样就可以在未来直接执行sbin中的那些不需要root权限的程序,而不再需要使用sudo前缀。
系统环境的PATH配置文件在/etc/profile文件中,打开它,文件是这样的:
  1. # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
  2. # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
  3. if [ "$(id -u)" -eq 0 ]; then
  4.   PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  5. else
  6.   PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
  7. fi
  8. export PATH
  9. if [ "${PS1-}" ]; then
  10.   if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
  11.     # The file bash.bashrc already sets the default PS1.
  12.     # PS1='\h:\w\$ '
  13.     if [ -f /etc/bash.bashrc ]; then
  14.       . /etc/bash.bashrc
  15.     fi
  16.   else
  17.     if [ "$(id -u)" -eq 0 ]; then
  18.       PS1='# '
  19.     else
  20.       PS1='$ '
  21.     fi
  22.   fi
  23. fi
  24. if [ -d /etc/profile.d ]; then
  25.   for i in /etc/profile.d/*.sh; do
  26.     if [ -r $i ]; then
  27.       . $i
  28.     fi
  29.   done
  30.   unset i
  31. fi
注意看这一段:
  1. if [ "$(id -u)" -eq 0 ]; then
  2.   PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  3. else
  4.   PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
  5. fi
  6. export PATH
这一段就是写如果是用root用户,那么PATH是/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这些路径;而如果是用非root用户,那么PATH是/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games这些路径。
第二个情况的路径我们可以看到没有包含sbin。为了让无论哪种登录情况都可以实现各种bin路径的访问,我们只需要将这个if判断换成如下代码,或者是在fi和 export PATH中添加如下代码就可以实现:
  1. PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
添加完后整个文件看起来是这样的:
  1. # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
  2. # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
  3. if [ "$(id -u)" -eq 0 ]; then
  4.   PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  5. else
  6.   PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
  7. fi
  8. PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
  9. export PATH
  10. if [ "${PS1-}" ]; then
  11.   if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
  12.     # The file bash.bashrc already sets the default PS1.
  13.     # PS1='\h:\w\$ '
  14.     if [ -f /etc/bash.bashrc ]; then
  15.       . /etc/bash.bashrc
  16.     fi
  17.   else
  18.     if [ "$(id -u)" -eq 0 ]; then
  19.       PS1='# '
  20.     else
  21.       PS1='$ '
  22.     fi
  23.   fi
  24. fi
  25. if [ -d /etc/profile.d ]; then
  26.   for i in /etc/profile.d/*.sh; do
  27.     if [ -r $i ]; then
  28.       . $i
  29.     fi
  30.   done
  31.   unset i
  32. fi
保存文件,运行source /etc/profile重新加载一下,就可以看到ifconfig可以直接执行了。


接着我们来看看第二个问题,每一次sudo都需要使用密码的问题。
我们当前用户在sudo组中,因此可以获得sudo权限,似乎再在使用sudo命令时输入密码并没有增加额外的安全性,反而让操作变得十分繁琐。因此我们可以修改配置来实现免密码使用sudo。
网上的一系列教程都告诉我们要修改/etc/sudoers这个文件,但当我们打开这个文件后,可以看到明晃晃的一句话:

这是官方在教我们正确的打开方式。那我们就听官方的,进入这个文件夹,新建一个文件,名字随便起,然后在里面写入以下内容:
  1. sipeed ALL=(ALL) NOPASSWD:ALL
完成后使用sudo reboot重启,重启后发现sodo不再要求输入密码。至此,配置完成,可以开始愉快的玩耍了。

1 举报

裸奔小耗子

2024-4-16 07:13:17
项目环境搭建

从这一期开始我们要正式开始做项目。项目的开发环境是python,Longan Pi中已经预先安装了Python 3.11。出于多用途使用考虑,我们最好给每一个项目新建一个虚拟环境,这样可以避免污染系统环境,同时避免了平台内多个项目的环境之间发生冲突。
要创建虚拟环境,我们要先安装python venv支持包。
  1. sudo apt install python3-venv
安装好后,新建项目文件夹,在项目文件夹中创建虚拟python环境
  1. cd ~
  2. mkdir jarvis
  3. cd jarvis
  4. python3 -m venv venv
运行完成后,我们用ls -l就可以看到文件夹下多出了个venv文件夹。这就是我们的虚拟环境。下面我们就要在这个虚拟环境中来安装一切项目所需的库文件,首先就是物体识别需要用到的YoloV8。
由于后续步骤比较费时,因此建议所有操作都在screen中完成,这样可以避免由于ssh掉线导致命令执行过程中断。
  1. screen -R project
  2. source venv/bin/activate
  3. pip3 install ultralytics
这个安装过程比较漫长,有部分库没有现成的whl,需要在本地编译。而其中psutil库在编译过程中可能由于缺少文件而报错。如果报错的话在报错信息中会提示需要通过apt来安装哪些库。我显示的错误信息如下:

一般情况下安装以下的库就可以解决问题。如果不行再去安装报错信息中显示的其他库。
  1. sudo apt install gcc python3-dev
安装好后,我们可以使用官方的测试命令来测试一下。
  1. yolo predict model=yolov8n.pt source='https://ultralytics.com/images/bus.jpg'
但不出意外的话这里就要出意外了。大概率这张图片我们无论如何都下载不下来。因此我们可以去随便百度张jpeg格式的照片,把命令中的照片链接替换为我们找的新照片。我测试使用的命令如下:
  1. yolo predict model=yolov8n.pt source='https://pics2.baidu.com/feed/d788d43f8794a4c2c0832f8b448ed6d8ac6e3972.jpeg'
运行完成后,如果没有报错,会有识别结果显示出来:

我们可以发现当前文件夹下出现了一个叫runs的目录,识别的结果就存在这个目录中。我们接下来可以用exit命令退出ssh,再使用scp命令把识别后的图片下载到D盘(这里我感觉有些繁琐,不知有没有更好的方法在ssh已连接的状态下直接传送文件)
  1. scp sipeed@192.168.199.124:~/jarvis/runs/detect/predict/d788d43f8794a4c2c0832f8b448ed6d8ac6e3972.jpeg D:\
传输完成后,如果显示为以下识别结果,那么说明我们Yolo环境已经可以正常工作。


1 举报

裸奔小耗子

2024-4-17 10:59:22
本帖最后由 裸奔小耗子 于 2024-4-17 14:57 编辑

音频采集

实现项目的第一步,就是音频采集。但在查阅官方wiki后,并没有看到音频相关部分;在板子上使用arecord -l he aplay -l查看也都没有发现相关设备。
因此,音频的实现思路是先在pc上运行唤醒词识别,识别到后开始录音,录音结束后进行语音识别。
当pc端完成语音识别后,建立在Longan Pi上的FastAPI服务器将识别到的文本发送至Longan Pi,由Longan Pi完成后续一系列的步骤。
最后,GPT返回的文本经由TTS转化为语音后,再通过FastAPI返回给发起request的PC,PC接收到返回的音频后进行播放,完成整个对话流程。
先来看PC部分,如果要使用python播放或录制音频,我们需要使用到ffmpeg。先从官方GitHub下载编译好的weindows build压缩包,shared和非shared都可以,这两个版本的区别就是非shared将dll都编译在了可执行文件中。
下载完成后,解压出来,然后最关键的一部来了,ffmpeg的可执行文件都在bin中,需要手动将bin文件夹加入环境变量中。
添加好后,还需要在python中安装相应的包,我是用的python版本是 Python 3.11
  1. pip install SpeechRecognition
  2. pip install pydub
  3. pip install pyaudio
首先,要根据环境噪音校准一下麦克风阈值,接下来开始采集。当音量超过阈值后还是录音,音量低于阈值一段时间后结束录音。随后将得到的音频传入语音识别模型转为文字,这里我使用的是google的模型:

  1. import speech_recognition as sr
  2. r = sr.Recognizer()
  3. with sr.Microphone() as source:
  4.     r.adjust_for_ambient_noise(source)
  5.     print(">说点什么:")
  6.     audio_in = r.listen(source)
  7.     print("Processing...")
  8. try:
  9.     text_input = r.recognize_google(audio_in, language="cmn-Hans-CN")
  10.     print("You said: " + text_input)
  11. except sr.UnknownValueError as error:
  12.     print("Google could not understand audio")
  13.     print(error)
  14.     text_input = None
  15. except sr.RequestError as error:
  16.     print("Could not request results from Google")
  17.     print(error)
  18.     text_input = None

运行一下,当terminal中出现“>说点什么:”后开始说话,随后就可以看到说话内容打印在terminal中。

当然了,大多数情况下,这个命令是无法执行的,原因是默认的google服务并没法直接连接。具体的解决方法在下一篇中我们会来详细聊聊。

在上面代码中使用r.adjust_for_ambient_noise(source)是为了根据环境情况自动设置阈值。当然,我们也可以自行进行设置,可以将其替换为下面的代码:
  1. r.dynamic_energy_threshold = False
  2. r.energy_threshold = 10000000
  3. r.pause_threshold = 1.2
这三行代码的作用分别是关闭自动阈值调整,设置阈值,与设置音量低于阈值多少秒后触发停止说话。
而具体的阈值大小,可以通过energy = audioop.rms(buffer, source.SAMPLE_WIDTH)来得到。

1 举报

裸奔小耗子

2024-4-17 11:11:59
本帖最后由 裸奔小耗子 于 2024-4-17 15:08 编辑

FastAPI服务器搭建与TTS播放

在这一篇中我们需要回到Longan Pi,搭建FastAPI服务器,通过服务器来和PC通信。
先安装对应的库:

  1. pip install -U fastapi uvicorn
这是一种方法,unicorn作为非同步运行库用来运行fastapi。当然根据fastapi的官方文档,还有一种更简单的方式进行安装:
  1. pip install -U "fastapi[all]"
但是根据github中的requirement文件看,这种方式进行安装更多其他的扩展依赖,主要用于Pydantic和Starlette。如果没有需求的话可以用第一种方式进行安装。
接着新建一个python文件,写下以下代码:
  1. from fastapi import FastAPI
  2. app = FastAPI()
  3. @app.get("/")
  4. async def mainapp(question):
  5.     return question
  6. if __name__ == "__main__":
  7.     import uvicorn
  8.     uvicorn.run(app, host="0.0.0.0", port=8080)
这样就创建好了简单的web服务器,可以通过get方法传入一个名为question的参数。运行起来,输入http://127.0.0.1:8080/?question=电子发烧友可以用浏览器进行测试:
PC端会通过这个question参数将识别到的语音转成文字传入;而在Longan Pi完成全部处理后,需要将文字转回语音返回给PC,那么这一步就需要用到TTS,文本转语音。我使用的是EDGE-TTS库,这个库使用非同步方法来处理流数据,刚好FastAPI也支持非同步方法。
一样的首先需要安装库:

  1. pip install edge-tts
安装完后,可以直接在终端输入命令来进行简单测试:

  1. edge-tts --voice zh-CN-XiaoyiNeural --text "你好啊,我是智能语音助手" --write-media hello_in_cn.mp3
运行完成后如果看到如下输出说明运行成功,当前目录下也会出现生成好的语音文件。
下面编写代码,我们可以做个简单的测试,在Longan Pi上搭建Web服务器,收到get请求后将参数转化为语音,返回给客户;PC作为客户端,语音识别后将文字内容发送给Longan Pi,然后收到返回后播放返回语音。
Longan Pi 部分代码:
  1. from fastapi import FastAPI
  2. from fastapi.responses import Response
  3. import edge_tts
  4. app = FastAPI()
  5. @app.get("/")
  6. async def mainapp(question):
  7.     voices = await edge_tts.VoicesManager.create()
  8.     voices = voices.find(ShortName="zh-CN-XiaoyiNeural")
  9.     communicate = edge_tts.Communicate(question, voices[0]["Name"])
  10.     out = bytes()
  11.     async for chunk in communicate.stream():
  12.         if chunk["type"] == "audio":
  13.             out += chunk["data"]
  14.         elif chunk["type"] == "WordBoundary":
  15.             pass
  16.     return Response(out, media_type="audio/mpeg")
  17. if __name__ == "__main__":
  18.     import uvicorn
  19.     uvicorn.run(app, host="0.0.0.0", port=8088)

PC端代码:

  1. import speech_recognition as sr
  2. import requests
  3. from pydub import AudioSegment
  4. from pydub.playback import play
  5. import io
  6. r = sr.Recognizer()
  7. while True:
  8.     with sr.Microphone() as source:
  9.         r.adjust_for_ambient_noise(source)
  10.         print(">说点什么:")
  11.         audio_in = r.listen(source)
  12.         print("Processing...")
  13.     try:
  14.         text_input = r.recognize_whisper(audio_in, model="small", language="chinese")
  15.         print("You said: " + text_input)
  16.     except sr.UnknownValueError:
  17.         print("Whisper could not understand audio")
  18.         text_input = None
  19.     except sr.RequestError as _error:
  20.         print("Could not request results from Whisper")
  21.         print(_error)
  22.         text_input = None
  23.     if text_input:
  24.         print('http://192.168.199.124:8088?question=' + text_input)
  25.         reply = requests.get('http://192.168.199.124:8088?question=' + text_input).content
  26.         audio_out = AudioSegment.from_file(io.BytesIO(reply), format="mp3")
  27.         play(audio_out)
其中PC端代码里的request get地址要改成Longan Pi的内网IP地址,PC与Longan Pi需要在同一个路由器下。
在语音识别部分,我们并没有使用官方的google在线方案,因为众所周知的原因无法使用。因此这里我们用了openai的离线方案whisper。需要在额外下载一个所需的库文件:

  1. pip install SpeechRecognition[whisper-local]
运行代码,不出意外的话,这里会报错:
分析一下报错就可以得出原因,虽然模型是本地离线运行的,但是如果本地没有对应的模型的话,会首先去下载模型,而模型下载又因为众所周知的原因失败了。那这里我们就要去手动解决一下。首先跟着报错信息找到库文件

  1. C:\Python310\lib\site-packages\speech_recognition\__init__.py
在文件中我们可以清晰的找到每一个模型对应的下载地址:
以及模型保存的路径:
因为模型比较大,无法上传到论坛,大家自行想办法把所需的模型下载下来,放到上面的路径中,再去重新运行,一切顺利的话,我们对着PC说话,很快就会听到我们的话被TTS复述回来。

1 举报

裸奔小耗子

2024-4-17 15:27:51
图像采集与物体识别

上一篇中完成了所有音频相关的输入输出,先在要开始尝试视频的相关操作了。我们可以利用手机上的摄像头,将其变成一个网络摄像头,然后再在Longan Pi上获取图像,就可以完成图像的采集。
首先先在手机上下载相关app,android手机的话,去应用商城搜索IP Webcam,如果没有的话,我也把apk放在附件中,大家直接下载就可以。
ip-webcam-1-17-15-868-multiarch.rar (13.41 MB)
(下载次数: 0, 2024-4-17 15:25 上传)

将手机连接到与Longan Pi相同的路由器下,然后打开软件,点击下图位置开启服务:

服务开启后,就可以看到连接地址:

按照一般情况,这里使用opencv来获取视频流就可以很好的实现想要的效果。但实际测试下来,由于每次运行图像识别耗时非常长,导致取的图片都是缓存中的图片,而不是最新拍摄的图片,查询了很多方法都无法解决,因此最后选择另外一种方式,先从摄像头的web管理界面得到拍照的jpeg地址,然后使用request get方法请求照片,这样可以确保每次获得的图像都是最新图像。
使用浏览器通过手机上的链接进入后,我们可以看到下面有个照相的按钮,通过这个链接的地址,我们就可以得到从python中获取最新图像的地址。
  1. from PIL import Image
  2. import requests
  3. import io
  4. img_raw = requests.get("http://192.168.199.143:8080/photoaf.jpg").content
  5. img = Image.open(io.BytesIO(img_raw))
通过以上代码,就可以得到所需的图像,接着使用我们一开始就部署好的YoloV8来进行图像识别,并把识别出的物品存到一个列表变量中:
  1. from ultralytics import YOLO
  2. model = YOLO('yolov8n.pt')
  3. results = model.predict(source=img, stream=True, save=True)
  4. for result in results:
  5.     detections = []
  6.     for box in result.boxes:
  7.         class_id = result.names[box.cls[0].item()]
  8.         cords = box.xyxy[0].tolist()
  9.         cords = [round(x) for x in cords]
  10.         confi = round(box.conf[0].item(), 2)
  11.         print("Object type:", class_id)
  12.         print("Coordinates:", cords)
  13.         print("Probability:", confi)
  14.         print("---")
  15.         detections.append({"name" : class_id, "cords" : cords, "conf" : confi})
将手机对准想要识别的场景,尝试运行一下,如果能看到如下输出,说明一切工作正常。

跟随输出的地址,可以找到识别后的图片:


1 举报

裸奔小耗子

2024-4-18 12:50:59
GPT接入

Github上有一个非常有名的开源项目,曾经还引来了不少争议,这个项目叫gpt4free。实际原理与poe wrapper相似,都是利用网络请求的方法从各个第三方平台的gpt接口来访问gpt。因此无需购买API KEY就可以直接使用。

但受限于国内的网络环境,大部分的第三方平台都无法访问,经过实测,这个库可能是唯一一个可以不通过特殊手段就可以直接访问到的GPT3.5 python api,但在访问的过程中由于会经理大量的失败重试过程,所以导致速度会比较慢,有时候还有一定概率访问不成功。

闲话说到这,接下来看看具体是怎么实现的。

首先使用pip安装库:
  1. pip install -U g4f
安装好后,我们可以写一个简单的对话机器人,来进行功能测试:
  1. from g4f.client import Client
  2. client = Client()
  3. messages = [{"role": "system", "content": "你是一名助手,你的任务是回答日常问题的解决方法,并提供有用的建议。"}]
  4. while True:
  5.     content = input(">说点什么:")
  6.     messages.append({"role": "user", "content": content})
  7.     response = client.chat.completions.create(
  8.     model="gpt-3.5-turbo",
  9.     messages=messages,
  10.     )
  11.     messages.append({"role": response.choices[0].message.role, "content": response.choices[0].message.content})
  12.     gpt_reply = messages[-1]["content"]
  13.     print(f"GPT 回答:{gpt_reply}")


如果大家在测试时实在无法联网,那么代码中client.chat.completions.create函数有一个可选变量proxy,大家可以使用这个变量自行解决。


功能整合,完成项目
上面这步完成后,项目所需的所有功能模块就都已经实现了。接下来我们把他们放到一起去。首先先是Longan Pi上的代码。

代码中首先使用FastAPI创建了一个服务器,服务器得到请求后,先从webcam中获取图像,获取到后进行图像识别,识别完成后再和请求中的问题进行字符串拼接,形成输入GPT的提问。接下来与GPT交互,交互完成后使用edge TTS把GPT返回的文字转为语音,再将语音bytes返回给发起get请求的客户端。
完整代码如下:
  1. from fastapi import FastAPI
  2. from fastapi.responses import Response
  3. from PIL import Image
  4. import requests
  5. import io
  6. from ultralytics import YOLO
  7. from g4f.client import Client
  8. import edge_tts
  9. app = FastAPI()
  10. model = YOLO('yolov8n.pt')
  11. client = Client()
  12. messages = [{"role": "system", "content": "你是一个人类助手,并且假设你拥有视力,可以看到东西。你的任务是根据user提供的你所看到的内容,回答user的问题。请在接下来的对话中隐藏你是计算机程序这件事,并且模仿的像一个具有视力的人类助手。不要提及你是一个语言模型,不要说提及你无法看到任何东西。"}]
  13. @app.get("/")
  14. async def mainapp(question):
  15.     #   GET IMAGE
  16.     img_raw = requests.get("http://192.168.199.143:8080/photoaf.jpg").content
  17.     img = Image.open(io.BytesIO(img_raw))
  18.     #   RECOGNITION
  19.     results = model.predict(source=img, stream=True)
  20.     for result in results:
  21.         detections = []
  22.         for box in result.boxes:
  23.             class_id = result.names[box.cls[0].item()]
  24.             cords = box.xyxy[0].tolist()
  25.             cords = [round(x) for x in cords]
  26.             confi = round(box.conf[0].item(), 2)
  27.             print("Object type:", class_id)
  28.             print("Coordinates:", cords)
  29.             print("Probability:", confi)
  30.             print("---")
  31.             detections.append({"name" : class_id, "cords" : cords, "conf" : confi})
  32.     #   GET QUESTION
  33.     if detections:
  34.         lst = []
  35.         for i in detections:
  36.             lst.append(i["name"])
  37.         obj = ",".join(lst)
  38.     else:
  39.         obj = "什么都没有"
  40.     content = "假如你看到了你的面前有以下这些东西:" + obj + "。请用中文,以人类助手的身份回答这个问题:" + question
  41.     #   GPT
  42.     messages.append({"role": "user", "content": content})
  43.     response = client.chat.completions.create(
  44.     model="gpt-3.5-turbo",
  45.     messages=messages
  46.     )
  47.     messages.append({"role": response.choices[0].message.role, "content": response.choices[0].message.content})
  48.     gpt_reply = messages[-1]["content"]
  49.     print(f"GPT 回答:{gpt_reply}")
  50.     #   TTS
  51.     voices = await edge_tts.VoicesManager.create()
  52.     voices = voices.find(ShortName="zh-CN-XiaoyiNeural")
  53.     communicate = edge_tts.Communicate(gpt_reply, voices[0]["Name"])
  54.     out = bytes()
  55.     async for chunk in communicate.stream():
  56.         if chunk["type"] == "audio":
  57.             out += chunk["data"]
  58.         elif chunk["type"] == "WordBoundary":
  59.             pass
  60.     return Response(out, media_type="audio/mpeg")
  61. if __name__ == "__main__":
  62.     import uvicorn
  63.     uvicorn.run(app, host="0.0.0.0", port=8080)
PC端的代码分为了两块,一块是连续图像识别,并显示出来,好让我们可以实时观察到GPT“看”到了什么:
  1. from PIL import Image
  2. import requests
  3. import io
  4. from ultralytics import YOLO
  5. model = YOLO('yolov8n.pt')
  6. while True:
  7.     #   GET IMAGE
  8.     img_raw = requests.get("http://192.168.199.143:8080/photoaf.jpg").content
  9.     img = Image.open(io.BytesIO(img_raw)).resize((1280, 720))
  10.     #   RECOGNITION
  11.     results = model.predict(source=img, stream=True, conf=0.5, show=True)
  12.     for result in results:
  13.         detections = []
  14.         for box in result.boxes:
  15.             class_id = result.names[box.cls[0].item()]
  16.             cords = box.xyxy[0].tolist()
  17.             cords = [round(x) for x in cords]
  18.             confi = round(box.conf[0].item(), 2)
  19.             print("Object type:", class_id)
  20.             print("Coordinates:", cords)
  21.             print("Probability:", confi)
  22.             print("---")
  23.             detections.append({"name" : class_id, "cords" : cords, "conf" : confi})
另一部分则是主程序部分,负责音频采集,识别,对Longan Pi发起请求,当收到回复的音频bytes后,对其进行播放。
  1. import speech_recognition as sr
  2. import requests
  3. from pydub import AudioSegment
  4. from pydub.playback import play
  5. import io
  6. r = sr.Recognizer()
  7. while True:
  8.     with sr.Microphone() as source:
  9.         r.dynamic_energy_threshold = False
  10.         r.adjust_for_ambient_noise(source)
  11.         print(">说点什么:")
  12.         audio_in = r.listen(source)
  13.         print("Processing...")
  14.     try:
  15.         text_input = r.recognize_whisper(audio_in, model="small", language="chinese")
  16.         print("You said: " + text_input)
  17.     except sr.UnknownValueError:
  18.         print("Whisper could not understand audio")
  19.         text_input = None
  20.     except sr.RequestError as _error:
  21.         print("Could not request results from Whisper")
  22.         print(_error)
  23.         text_input = None
  24.     if text_input:
  25.         reply = requests.get('http://192.168.199.124:8080?question=' + text_input).content
  26.         audio_out = AudioSegment.from_file(io.BytesIO(reply), format="mp3")
  27.         play(audio_out)
至此,整个项目完成。

我拍了一段演示视频放在了下面,视频未做剪辑加速,可以让大家直观的感受到图像识别与GPT请求所需的时间。虽然比较慢,但整套流程能在Longan Pi这样一个低功耗的小巧linux平台跑起来,我已经觉得非常满意。

最后,将项目的源代码附上:
jarvis.rar (13.41 MB)
(下载次数: 0, 2024-4-18 12:50 上传)


1 举报

更多回帖

发帖
×
20
完善资料,
赚取积分