前言

关于ESP32

ESP32是一款低成本的高性能单片机,支持2.4GHz的WIFI、蓝牙,可以做很多有意思的物联网产品

ESP8266相信大家都很熟悉了,ESP32比8266强多了

description

ESP32模组是一款非常受欢迎的模组,可能是由于其低廉的价格(仅需十多元)与较高的性能(双核处理器,240Mhz主频)

360截图16810304334270

关于ESP32-CAM

基于ESP32模组的ESP32-CAM开发板提供了一种低成本的无线摄像头实现方式

360截图1843070797101105

我购买的ESP32CAM如下

21元,带OV2640摄像头

要让它跑起来,还需要一个USB转UART,用于下载(烧录)程序

1029212224

开始

安装Arduino IDE

因为不想配官方IDE环境,所以这里偷懒选了Arduino IDE

Arduino IDE 2.0看起来好新的样子,趁这次机会试一下新版

  • 访问Arduino官网

360截图175711157981122

  • 选择2.0版本

360截图18030519333854

360截图18500819107127138

  • 下载完、解压之后双击Arduino IDE.exe

360截图184701297611977

  • 如下,即安装完成

    新版比旧版启动速度快很多

360截图17290506073407

安装ESP32-CAM的板卡库

打开首选项

360截图18231115745586

在下面的框框填入如下网址

1
https://dl.espressif.com/dl/package_esp32_index.json

360截图18030512092308

360截图18030517857787

完成之后点击下面图片中箭头所指的地方

360截图180305127510089

输入ESP32即可看到如下结果

如果没有显示,可以重启下Arduino IDE

360截图18030521457640

点击安装

360截图184307108591106

出现下面情况说明正在下载

360截图17360624064527

通过观察json,发现要下三个依赖包

360截图16720406559174

如果出现下面情况,说明没有在下载

再次点击install即可

  • 情况一

360截图171205218982107

  • 情况二

360截图1626100680131116

下面这样则是正常下载

360截图17710410739395

进度条在走,需要好长一段时间,挂了梯子应该会快一点

360截图17040509427878

下载第二个组件esptool,用于给esp32进行程序烧录

360截图18430706100127128

下载第三个包:SPI File system

360截图1672040597100121

好不容易安装完了

360截图18430710347056

编译程序

选择板卡

随便选一个即可

Tools->Board->esp32

360截图16720328759498

打开示例程序

找到ESP32 Camera示例

File>Examples>ESP32->Camera->CameraWebServer

360截图18030516756097

修改示例程序

先编译一遍看看潜在错误

编译还是一如既往的耗时间,一点都没改

360截图1878041510911897

出错了,这个报错说的是编译出来的程序大小超过了所选ESP32开发板的存储程序的空间大小

看来不能随便选开发板

360截图17340906102122114

重选一下开发板

Tools->Board->esp32->AI Thinker ESP32-CAM

360截图18720116103155109

编译通过

360截图17290506707885

修改wifi的SSID和密码为自己的

360截图17040516387851

连接与程序烧录

连接

USB转UART转换器TX接ESP32-CAM的RX

USB转UART转换器RX接ESP32-CAM的TX

3.3V接ESP32-CAM的VCC(或者5V接5V,因为板上有AMS1117-3.3用于将5V降压到3.3V)

GND接GND

1029212307a

插上电脑

如果不知道哪个COM,可以去设备管理器看

360截图17770704262152

选择正确的COM

如果没有port选项,而且确定设备管理器中识别到了端口,就稍等一会,它会自己出现

360截图17630327404946

进入下载模式

将IO0与GND连接起来,并按下ESP32-CAM上的RST键

1029212330

RST键在这里

1029212331

编译并烧录

按下箭头所指按键

360截图16720401216822

烧录完成

360截图17001016221629

打开串口监视器

360截图170010159711180

设置波特率115200

360截图17571116227745

拔掉IO0和GND的连接线,按下RST使其正常启动

错误修正

遇到下面这种情况,先拔掉电源,再重新插拔下摄像头

360截图17290505619783

发现无效,找到以下片段,则修改为以下所示

360截图17731205836983

成功启动

360截图16571227296619

效果

访问

打开上图所示地址

360截图17001016252561

开始摄像

点击start stream即可看到图像

光线不太好,比较暗

经过测试,实际效果其实还是可以的,蛮清晰的

360截图18720118124737

更多操作

为了更好地使用,可以对代码进行修改

设置静态IP

有时候我们会有固定IP的需求(方便标号管理)

如果不太明白IP是什么,请不要操作

下面代码可以将IP地址固定为192.68.1.11

1
2
3
4
5
WiFi.mode(WIFI_STA);
IPAddress Local(192,168,1,11);
IPAddress Gateway(192,168,1,1);
IPAddress Subnet(255,255,255,0);
WiFi.config(Local,Gateway,Subnet);

添加到此处(wifi begin前)

360截图17571115091922

修改成功

360截图18241013223343

添加WIFI断线重连功能

代码1

1
2
3
4
5
6
7
8
9
10
unsigned long wifi_recon_time = 0;
void check_and_recon_wifi() {
if (WiFi.status() != WL_CONNECTED) {//如果WIFI未连接
if (millis() - wifi_recon_time > 25000) {//超过25秒再次尝试
WiFi.begin(ssid, password);
wifi_recon_time = millis();
Serial.println("Try to reconnect wifi");
}
}
}

添加到此处

360截图17380401498879

代码2

1
check_and_recon_wifi();

此处改为下图所示

360截图18770601869886

WEB界面控制LED

有空再更

制作简单的客户端

使用Python3实现了几个小功能

(保存一张图片)get_jpeg.py

运行即可保存一张图片

需要安装requests包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests





Camera_IP = "192.168.1.10"

def load_url(url):
response=requests.get(url)
return response.content

def save_img(url):
image=load_url("http://" + url + "/capture?_cb=0")
with open('./cap.jpg','wb') as fb:
fb.write(image)


save_img(Camera_IP)

(自动化设置摄像头)set_cam.py

设置摄像头并保存一张图片

这里只提供了分辨率和ISO调节以及两个我用的上的按钮,

其它设置大同小异,改改代码即可

需要requests、json包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import requests, json


Camera_IP = "192.168.1.10"

class Set_Cam:
def __init__(self, IP_ADDR):
self.ip_addr = IP_ADDR


def set_Resolution(self, set_resol):
desc = ["UXGA(1600x1200)",
"SXGA(1280x1024)",
"XGA(1024x768)",
"SVGA(800x600)",
"VGA(640x480)",
"CIF(400x296)",
"QVGA(320x240)",
"HQVGA(240x176)",
"QQVGA(160x120)"]

desc1 = [[1600, 1200],
[1280, 1024],
[1024, 768],
[800, 600],
[640, 480],
[400, 296],
[320, 240],
[240, 176],
[160, 120]]

select_di = {"QQVGA" : 0, "HQVGA" : 1, "QVGA" : 2, "CIF" : 3, "VGA" : 4, "SVGA" : 5, "XGA" : 6, "SXGA" : 7, "UXGA" : 8}
Resolution_val = [0,3,4,5,6,7,8,9,10] #QQVGA HQVGA QVGA CIF VGA SVGA XGA SXGA UXGA

print("SET SIZE TO", desc[8-select_di[set_resol]])
self.send_setting("framesize", Resolution_val[select_di[set_resol]])
return desc1[8-select_di[set_resol]]

def set_GainCeiling(self, set_gc):
desc = ["2X",
"4X",
"8X",
"16X",
"32X",
"64X",
"128X"]

select_di = {"2X" : 0, "4X" : 1, "8X" : 2, "16X" : 3, "32X" : 4, "64X" : 5, "128X" : 6}
GainCeiling_val = [0,1,2,3,4,5,6]

print("SET GainCeiling TO", desc[select_di[set_gc]])
self.send_setting("gainceiling", GainCeiling_val[select_di[set_gc]])

def set_AECDSP(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET AEC DSP TO", desc[select_di[set_SET]])
self.send_setting("aec2", select_di[set_SET])

def set_RawGMA(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET Raw GMA TO", desc[select_di[set_SET]])
self.send_setting("raw_gma", select_di[set_SET])

def set_HMirror(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET H-Mirror TO", desc[select_di[set_SET]])
self.send_setting("hmirror", select_di[set_SET])

def set_VFlip(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET V-Flip TO", desc[select_di[set_SET]])
self.send_setting("vflip", select_di[set_SET])

def get_state(self):
response=requests.get("http://" + self.ip_addr + "/status")
if response.status_code != 200:
return {}
return json.loads(response.content)

def send_setting(self, ctrl, val):
state_got = self.get_state()[ctrl]
if state_got != val:
response=requests.get("http://" + self.ip_addr + "/control?var={}&val={}".format(ctrl, val))
if response.status_code != 200:
print("Fail to set {}".format(ctrl))
else:
print("Set {} done".format(ctrl))
else:
print("Set {} already finished".format(ctrl))





mycam = Set_Cam(Camera_IP)
resol = mycam.set_Resolution("UXGA")
mycam.set_GainCeiling("64X")
mycam.set_AECDSP("ON")
mycam.set_RawGMA("ON")
mycam.set_VFlip("ON")
mycam.set_HMirror("ON")



def load_url(url):
response=requests.get(url)
return response.content

def save_img(url, filename):
image=load_url("http://" + url + "/capture?_cb=0")
with open('./' + filename,'wb') as fb:
fb.write(image)



save_img(Camera_IP, 'cap.jpg')

运行之后

360截图18430705388542

保存的图像

cap

(获取实时图像)get_jpeg_stream.py

获取实时图像

需要opencv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import requests, json
import cv2

Camera_IP = "192.168.1.10"

class Set_Cam:
def __init__(self, IP_ADDR):
self.ip_addr = IP_ADDR


def set_Resolution(self, set_resol):
desc = ["UXGA(1600x1200)",
"SXGA(1280x1024)",
"XGA(1024x768)",
"SVGA(800x600)",
"VGA(640x480)",
"CIF(400x296)",
"QVGA(320x240)",
"HQVGA(240x176)",
"QQVGA(160x120)"]

desc1 = [[1600, 1200],
[1280, 1024],
[1024, 768],
[800, 600],
[640, 480],
[400, 296],
[320, 240],
[240, 176],
[160, 120]]

select_di = {"QQVGA" : 0, "HQVGA" : 1, "QVGA" : 2, "CIF" : 3, "VGA" : 4, "SVGA" : 5, "XGA" : 6, "SXGA" : 7, "UXGA" : 8}
Resolution_val = [0,3,4,5,6,7,8,9,10] #QQVGA HQVGA QVGA CIF VGA SVGA XGA SXGA UXGA

print("SET SIZE TO", desc[8-select_di[set_resol]])
self.send_setting("framesize", Resolution_val[select_di[set_resol]])
return desc1[8-select_di[set_resol]]

def set_GainCeiling(self, set_gc):
desc = ["2X",
"4X",
"8X",
"16X",
"32X",
"64X",
"128X"]

select_di = {"2X" : 0, "4X" : 1, "8X" : 2, "16X" : 3, "32X" : 4, "64X" : 5, "128X" : 6}
GainCeiling_val = [0,1,2,3,4,5,6]

print("SET GainCeiling TO", desc[select_di[set_gc]])
self.send_setting("gainceiling", GainCeiling_val[select_di[set_gc]])

def set_AECDSP(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET AEC DSP TO", desc[select_di[set_SET]])
self.send_setting("aec2", select_di[set_SET])

def set_RawGMA(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET Raw GMA TO", desc[select_di[set_SET]])
self.send_setting("raw_gma", select_di[set_SET])

def set_HMirror(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET H-Mirror TO", desc[select_di[set_SET]])
self.send_setting("hmirror", select_di[set_SET])

def set_VFlip(self, set_SET):
desc = ["OFF",
"ON"]
select_di = {"OFF" : 0, "ON" : 1}
print("SET V-Flip TO", desc[select_di[set_SET]])
self.send_setting("vflip", select_di[set_SET])

def get_state(self):
response=requests.get("http://" + self.ip_addr + "/status")
if response.status_code != 200:
return {}
return json.loads(response.content)

def send_setting(self, ctrl, val):
state_got = self.get_state()[ctrl]
if state_got != val:
response=requests.get("http://" + self.ip_addr + "/control?var={}&val={}".format(ctrl, val))
if response.status_code != 200:
print("Fail to set {}".format(ctrl))
else:
print("Set {} done".format(ctrl))
else:
print("Set {} already finished".format(ctrl))





mycam = Set_Cam(Camera_IP)
resol = mycam.set_Resolution("UXGA")
mycam.set_GainCeiling("64X")
mycam.set_AECDSP("ON")
mycam.set_RawGMA("ON")
mycam.set_VFlip("ON")
mycam.set_HMirror("ON")


cap = cv2.VideoCapture("http://" + Camera_IP + ":81/stream")
while cap.isOpened():
ret, frame = cap.read()
cv2.imshow("capture", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()
cv2.destroyAllWindows()

运行效果如下,窗口显示实时图像

360截图18430708305577

END

就到这里了

EOF