米尔iMX6ULL开发板评测之监控平台
米尔的茜茜小姐姐给我提供了一块米尔王牌产品MYD-Y6ULX-V2开发板供评测,查看开发板上的具体型号标注,可以了解到是:MYD-Y6ULY2-V2-4E512D-50-I,内核是iMX6ULL。
从硬件使用手册可以了解到,这个型号是一款工业级的产品,能工作在-40~+85°C。其内存为512MB DDR3L,板载存储是4GB eMMC Flash。
评测规划
拿到这个板子后,看到板子上带有SIM卡的插槽,于是结合我之前的使用米尔板子的经验,想制作一个远程监控的平台。
不过通过硬件手册了解到,板子自身不带有LTE模块,需要安装一个Mini PCI-E接口的LTE 模块才能够使用4G网络,且仅支持仅支持移远EC20型号。
而手头暂时没有这个型号的LTE模块,于是退而且其次,使用有线网络来进行数据的传输。
这块板子的full镜像,提供了通过V4L对USB摄像头的支持,直接插接上USB摄像头就能使用了。
最终,具体的评测规划如下:
- 使用米尔MYD-Y6ULX-V2开发板提供摄像头监控数据
- 使用OLED呈现开发板的设备负载、IP地址和服务信息
- 使用PyQT5开发监控显示界面
- 使用opencv进行人脸识别检测
硬件准备
- 海康威视USB摄像头
- SSD1306 OLED:
- MYD-Y6ULX-V2开发板支持IIC和SPI通讯,我手头正好有合适的IIC通讯的SSD1306 OLED用上
- USB2TTL:
- 开始使用MYD-Y6ULX-V2开发板的时候,需要使用串口连接做一些基础的设置
- 米尔MYD-Y6ULX-V2开发板
- 将以上设备都连接到开发板以后,具体如下:
- 具体的配置连线,后续会进行说明;具体的接口分布如下:
镜像烧录
MYD-Y6ULX-V2开发板默认已经少录了系统,但是属于较老的版本,我们需要自己烧录为最新的版本。
根据使用手册,选用如下的镜像,并按照手册设定好启动方式,进行烧录更新。
- 镜像类型:
- 烧录方式:
- 上述镜像需要先解压,然后使用官方提供的Win32 Disk Imager烧录到SD卡,然后接到开发板上
- 拨码开关
- MYD-Y6ULX-V2开发板有几个不同的子型号,以及设置不同的启动方式的时候,需要通过板载的拨码开关来控制。我评测的开发板,为eMMC版本,所以更新选择
TF Card启动(eMMC版本)模式
,具体如下:
- 连接调试串口:
- 参考之前的接口分布,使用USB2TTL连接开发板的调试串口和电脑:
- 在电脑端,使用串口终端工具进行连接:
- Windows系统可以视同使用Putty或者mobaxterm,Linux和macOS系统可以使用minicom或者picocom
- 我用的是macOS,所以使用picocom连接:
- 烧录:
- 关机装状态下,插好SD卡后,重新上电开机,就会自动启动,并进刷机状态,并输出正在升级的提示信息。升级需要一段时间,请耐心等待。
- 验证:
- 更新成功后,断电,然后将拨码开关设置回
eMMC启动
模式,重启上电开机,最终会看到如下的启动信息,其中NXP i.MX Release Distro 5.10-gatesgarth
表示已经更新到了当前系统:
开发板设置
在更新完毕后,需要对开发板进行一些基础的设置,以方便后续进一步操作。
- eth0、eth1的IP设置:
-
参考:7.2. 通用网络配置
-
使用vi修改文件:/etc/systemd/network/10-static-eth0.network
-
使用vi修改文件:/etc/systemd/network/11-static-eth1.network
-
静态配置示例如下:
[Match]
Name=eth0
[Network]
Address=192.168.1.177/24
Gateway=192.168.1.1
DNS=192.168.1.1
-
动态配置示例如下:
[Match]
Name=eth0
[Network]
DHCP=yes
EOF
-
配置后,可以重启网络服务,然后查看结果:
service systemd-networkd restart
ifconfig
- 时区设置:
- ntpd对时设置
- 参考:ntpd时钟同步服务
- 使用vi修改:/etc/sysconfig/ntpd
# Drop root to id 'ntp:ntp' by default.
OPTIONS="-u ntp:ntp -p /var/run/ntpd.pid"
# Set to 'yes' to sync hw clock after successful ntpdate
SYNC_HWCLOCK=yes #make no into yes; BIOS的时间也会跟着修改
# Additional options for ntpdate
NTPDATE_OPTIONS=""
- 使用vi修改:/etc/ntp.conf
restrict default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict 10.0.0.0 mask 255.0.0.0 nomodify motrap
restrict 192.168.0.0 mask 255.255.255.0 nomodify motrap
restrict 192.168.1.123 mask 255.255.255.255 nomodify motrap
server cn.pool.ntp.org prefer
server 0.asia.pool.ntp.org
server 3.asia.pool.ntp.org
server 0.centos.pool.ntp.org iburst
server 127.127.1.0 # local clock
fudge 127.127.1.0 stratum 10
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys
logfile /var/log/ntp.log
- 重启服务生效:
service ntpd restart
date
- 查看结果:
- 远程连接:
以上设置完成后,就可以在其他电脑上,使用ssh远程连接,来连接开发板了。
- 首先在开发板上,为root用户设置密码:
passwd root
- 然后,在其他电脑上,使用ssh工具进行远程连接:
开发环境建立
米尔为MYD-Y6ULX-V2开发板提供了详细的开发环境建立的指导,参考《MYD-Y6ULX_Linux软件开发指南.pdf》即可完成所需要的工作。
开发环境需要在一个Ubuntu环境下建立,而不是开发板自身的系统上。
- Ubuntu开发环境建立
-
从开发板的资料包页面,下载光盘镜像:http://down.myir-tech.com/MYD-Y6ULX/,也可以从百度网盘下载相应的资料包。
-
首先安装基础工具包,并进行工作目录的构建:
sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev
mkdir -p ~/MYD-Y6ULX-devel
export DEV_ROOT=~/MYD-Y6ULX-devel
sudo mkdir /media/cdimage
sudo mount -o loop ~/Downloads/MYD-Y6ULX_L5.10.9_20220826.iso /media/cdimage
cp -r /mnt/cdimage/02_Images $DEV_ROOT/
cp -r /mnt/cdimage/03_Tools $DEV_ROOT/
cp -r /mnt/cdimage/04_Sources $DEV_ROOT/
-
安装编译链
- 编译链:
- 根据手册的说明,使用full系统的应用工具链,以便进行应用的交叉编译
cd $DEV_ROOT/03_Tools/Tools_chain/
bash fsl-imx-fb-glibc-x86_64-myir-image-full-cortexa7t2hf-neon-myd-y6ull14x14-toolchain-5.10-gatesgarth.sh
. /opt/test5.10/environment-setup-cortexa7t2hf-neon-poky-linux-gnueabi
$CC -v
- 安装编译工具链时的提示:安装到/opt/test5.10目录
- 安装完成后的验证:
mjpg_streamer部署
要使用到摄像头,并对外提供监控数据,使用mjpg_streamer最合适了。
在网上也有不少 iMX6移植原版mjpeg-streamer的文章可供参考,不过我查看后大受误导。
直接使用我之前的版本,进过简单的修改,就能使用MYD-Y6ULX-V2开发板的编译工具链进行成功编译了。
ssd1306_bin部署
我所使用的SSD1306 OLED使用IIC通讯的,之前我也使用过一个Linux环境下的ssd1306工具。
经过尝试,这个工具可以使用MYD-Y6ULX-V2开发板的编译工具链进行编译和使用。
- 下载地址:https://github.com/armlabs/ssd1306_linux/archive/refs/heads/master.zip
- 交叉编译:
- 硬件连接
- 参考手册上的说明,进行硬件的连接。我所使用的OLED使用到了VDD_5V、DGND、I2C2_SCL、I2C2_SDA。务必要注意所使用的OLED的电压,有的只能使用3.3V,使用5V会完蛋。
- OLED显示测试
上述硬件连接完成后,就可以远程连接到开发板,进行测试了。
-
查看OLED设备是否成功硬件连接,通常IIC地址为3c:
i2cdetect -y -a 1
-
然后,进行显示测试:
cd ~/mjpeg_server
./ssd1306_bin -n 1 -I 128x64
./ssd1306_bin -n 1 -c
./ssd1306_bin -n 1 -r 0
./ssd1306_bin -n 1 -x 0 -y -0 -m "Hello World!\n\nI'm MYD-Y6ULX-V2."
开发板应用综合部署
完成以上两项工作,开发板部分的基础工作就完成了,可以写一个启动脚本来进行控制,具体如下:
为了安全访问,在脚本中设置了访问的用户名和密码,可以根据实际需要进行修改。
以下的操作,都需要远程连接到开发板上进行。
-
服务启动脚本:~/mjpeg_server/mjpeg_server_start.sh
#!/bin/bash
cd "${0%/*}"
killall mjpg_streamer >/dev/nul 2>&1
device=$(v4l2-ctl --list-devices | grep 'Camera' -A1 | grep /dev/video | head -1 | awk '{print $NF}')
./mjpg_streamer -i "input_uvc.so -d $device -n -r 640x480 -f 10" -o "output_http.so -w ./ -c test:test123" &
./ssd1306_bin -n 1 -I 128x64
./ssd1306_bin -n 1 -r 0
let count=0
while true
do
nowdate="$(date '+%Y-%m-%d %H:%M:%S')"
load="$(w | head -1 | sed -e 's/^.*average: //' | cut -d ',' -f 1)"
temp=$(echo "scale=1;$(cat /sys/devices/virtual/thermal/thermal_zone0/temp)/1000" | bc)
ipstr=" ${nowdate}\n L:${load} T:${temp}"
if [[ $count -gt 0 ]];then
./ssd1306_bin -n 1 -x 0 -y 0 -m "${ipstr}"
else
./ssd1306_bin -n 1 -c
ipstr="${ipstr}\n**-*-IP Address-*-**"
i=0
for ip in $(ip addr show | grep -v "127.0.0.1" | awk -F'[ /]+' '{if($0 ~ / inet /) print $3;}')
do
let i=i+1
ipstr="${ipstr}\nIP${i}: ${ip}"
done
ipstr="${ipstr}\nSRV: ip:8080/?action"
ipstr="${ipstr}\n =stream"
echo -e "${ipstr}"
./ssd1306_bin -n 1 -x 0 -y 0 -m "${ipstr}"
fi
let count=count+1
if [[ $count -gt 15 ]];then
let count=0
fi
sleep 1
done
-
开机启动:
PyQT5应用开发
MYD-Y6ULX-V2开发板的full环境支持使用QT5进行应用开发,但实际使用中,需要屏幕配合。
我手头没有对应的屏幕,所以这一步的工作,就在电脑上进行,并使用PyQT5进行开发。
具体要做的工作如下:
其中人脸识别部分,参考了:* opencv快速入门人脸检测与人脸识别
涉及到具体的代码的开发,我就直接上代码了,感兴趣的同学,可以查看代码进行学习。
from PyQt5 import QtWidgets
from PyQt5.QtGui import QImage, QPixmap, QKeySequence
from PyQt5.QtCore import QThread
import sys, cv2, threading, random, signal
import numpy as np
import socket
import time, datetime
import requests
from requests.auth import HTTPBasicAuth
CAMERA_SOURCE = 2
CAMERA_LOCAL_INDEX = 0
CAMERA_SOCKET_PORT = 8888
CAMERA_REMOTE_URL = "http://192.168.1.177:8080/?action=stream"
CAMERA_SOURCE_NAME = ["USB摄像头", "网络图像流", "米尔MYD-Y6ULX-V2摄像头监控"]
AUTH_CONFIG = {"user":"test","pass":"test123"}
FACE_DETECTION = True
if FACE_DETECTION == True:
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
face_cascade.load('./haarcascades/haarcascade_frontalface_default.xml')
face_box_colors = [
(255, 0, 0),
(0, 255, 0),
(0, 255, 0),
(255, 255, 0),
(255, 0, 255),
(0, 255, 255),
(255, 128, 128),
(128, 255, 128),
(128, 255, 128),
(255, 255, 128),
(255, 128, 255),
(128, 255, 255)
]
app = QtWidgets.QApplication(sys.argv)
window_w, window_h = 640, 480
scale = 0.58
Form = QtWidgets.QWidget()
Form.setWindowTitle(CAMERA_SOURCE_NAME[CAMERA_SOURCE])
Form.resize(window_w, window_h)
def windowResize(self):
global window_w, window_h, scale
window_w = Form.width()
window_h = Form.height()
label.setGeometry(0,0, window_w, int(window_w*scale))
btn1.setGeometry(10, window_h-40,70,30)
btn2.setGeometry(80, window_h-40,70,30)
btn3.setGeometry(window_w - 80, window_h-40,70,30)
Form.resizeEvent = windowResize
ocv = True
def closeOpenCV(self):
global ocv, output
ocv = False
print("关闭程序")
try:
output.release()
except:
pass
Form.closeEvent = closeOpenCV
label = QtWidgets.QLabel(Form)
label.setGeometry(0,0, window_w, int(window_w*scale))
def rename():
return datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
photo = False
def takePhoto():
global photo
photo = True
print("马上拍照")
btn1 = QtWidgets.QPushButton(Form)
btn1.setGeometry(10, window_h-40,70,30)
btn1.setText('拍照')
btn1.clicked.connect(takePhoto)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
recorderType = False
def recordVideo():
global recorderType, output
if recorderType == False:
output = cv2.VideoWriter(f'videos/{rename()}.mp4', fourcc, 20.0, (window_w, int(window_w*scale)))
recorderType = True
btn2.setGeometry(80, window_h-40,200,30)
btn2.setText('录像中,点击停止保存')
else:
output.release()
recorderType = False
btn2.setGeometry(80, window_h-40,70,30)
btn2.setText('录像')
btn2 = QtWidgets.QPushButton(Form)
btn2.setGeometry(80, window_h-40,70,30)
btn2.setText('录像')
btn2.clicked.connect(recordVideo)
def quitApp():
global video_server
print("退出程序")
closeOpenCV(False)
app = QtWidgets.QApplication.instance()
app.quit()
btn3 = QtWidgets.QPushButton(Form)
btn3.setGeometry(window_w-80, window_h-40,70,30)
btn3.setText('退出')
btn3.clicked.connect(quitApp)
def face_detection_process(frame):
if FACE_DETECTION == True:
face_count = 0
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
color = face_box_colors[face_count % len(face_box_colors)]
cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
face_count+=1
def mjpeg_remote_server():
global window_w, window_h, scale, photo, output, recorderType, ocv
r = requests.get(CAMERA_REMOTE_URL, auth=HTTPBasicAuth(AUTH_CONFIG["user"], AUTH_CONFIG["pass"]), stream=True)
if(r.status_code != 200):
print("Received unexpected status code {}".format(r.status_code))
return
count = 0
is_first = False
recv_data_mjpeg = bytes()
for recv_data in r.iter_content(chunk_size=1024):
if not ocv:
break
count+=1
if count % 10000 == 1:
print("\trecv stream success")
recv_data_mjpeg += recv_data
a = recv_data_mjpeg.find(b'\xff\xd8')
b = recv_data_mjpeg.find(b'\xff\xd9')
if not (a != -1 and b != -1):
continue
mjpg_data_raw = recv_data_mjpeg[a:b+2]
recv_data_mjpeg = recv_data_mjpeg[b+2:]
mjpeg_data = np.frombuffer(mjpg_data_raw, 'uint8')
img = cv2.imdecode(mjpeg_data, cv2.IMREAD_COLOR)
if not is_first:
is_first = True
sp = img.shape
sz1 = sp[0]
sz2 = sp[1]
sz3 = sp[2]
print('网络图像: width=%d \theight=%d \tnumber=%d' % (sz1, sz2, sz3))
scale = sz1/sz2
frame = cv2.resize(img, (window_w, int(window_w*scale)))
if photo == True:
name = rename()
name_save = f'photos/{name}.jpg'
print("照片存储:%s" % name_save)
cv2.imwrite(name_save, frame)
photo = False
if recorderType == True:
output.write(frame)
face_detection_process(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height, width, channel = frame.shape
bytesPerline = channel * width
img = QImage(frame, width, height, bytesPerline, QImage.Format_RGB888)
label.setPixmap(QPixmap.fromImage(img))
if CAMERA_SOURCE == 2:
video_server = QThread()
video_server.run = mjpeg_remote_server
video_server.start()
Form.show()
sys.exit(app.exec_())
- 设置好开发板的MJPEG视频地址,然后启动上面的python程序,就能打开如下界面了:
- 如果画面中有人脸,就会自动识别了:
参考资料