<think>我们有两个代码片段,代码1是基于YOLOv5的目标检测,代码2是基于PP_OCR的文本识别。
任务要求:修改代码1,将其中使用YOLOv5模型识别物体的逻辑替换为使用OCR模型(如代码2所示)的识别逻辑。
具体规则:
1. 当OCR识别到文本为"01"、"02"、...、"07"时,执行原代码1中`elif 1 <= obj.class_id <= 18:`对应的逻辑(即处理普通标签的逻辑)。
2. 当OCR识别到文本为"08"时,执行原代码1中`if obj.class_id == 19`的逻辑(即处理特殊标签19的逻辑)。
注意:原代码1中,YOLOv5检测返回的是物体检测结果(objs,每个obj有class_id等属性),而OCR检测返回的是文本识别结果(objs,每个obj有char_str()方法返回识别到的字符串)。
因此,我们需要做以下修改:
- 将模型从YOLOv5改为PP_OCR,并按照代码2的方式初始化OCR模型。
- 修改主循环中的检测部分,使用OCR模型进行识别。
- 在得到OCR识别结果后,遍历每一个识别到的文本对象,判断其字符串是否为"01"到"08"(注意:OCR识别到的字符串可能是数字字符串,但也要注意可能有误识别,所以需要判断是否在指定集合中)。
- 根据识别到的字符串,触发相应的逻辑(相当于原代码中的class_id判断)。
但是,原代码1中,每个检测到的对象(obj)都有一个class_id,而OCR识别到的每个文本对象有一个字符串。我们需要将字符串映射到原逻辑中的class_id范围:
字符串"01"到"07"对应原逻辑中的class_id范围1到18(注意:原逻辑中1到18是多个标签,但这里我们实际上只需要知道是普通标签即可,因为原逻辑中对于1到18的处理是一样的,都是按照普通标签处理,而具体是哪个数字并不影响处理流程,因为原逻辑中上传时使用的是obj.class_id。但现在我们使用OCR,识别到的字符串需要转换为一个数字,然后模拟成class_id吗?)
然而,原代码1中,上传图片时保存的文件名和上传数据时使用的标签是class_id(即数字)。现在,我们使用OCR识别到的字符串(如"01")实际上对应一个数字标签(比如1),但注意原代码1中标签1到18的处理方式相同,所以我们可以这样处理:
将OCR识别到的字符串(去掉前导零)转换为整数,然后这个整数就作为模拟的class_id。但是注意,原代码中标签19对应特殊处理,而我们现在要求当识别到"08"时执行原class_id==19的逻辑。
因此,我们可以设计:
- 如果识别到的字符串是"01"到"07",则将其转换为整数(如int("01")=1),然后这个整数在1到7之间,符合原代码中1<=class_id<=18的条件,所以会触发普通标签的处理。
- 如果识别到的字符串是"08",我们将其视为特殊标签,相当于原class_id=19。
但是,原代码1中,普通标签的范围是1到18,而我们现在只有1到7(对应字符串01-07)和08(对应特殊标签19)。所以,我们可以在handle_detection函数中,将OCR识别到的字符串映射为一个模拟的class_id:
如果是"01"到"07",则class_id = int(text) # 得到1到7
如果是"08",则class_id = 19
这样,我们就可以复用原handle_detection函数中的状态机逻辑,因为该函数是根据class_id的值进行判断的。
步骤:
1. 替换模型加载部分:将YOLOv5模型加载改为OCR模型加载。
2. 修改主循环中的检测部分:使用OCR模型进行检测,然后对检测到的每个文本对象进行映射,生成一个类似原YOLOv5检测结果的对象(我们用一个简单的类来模拟,只需要有class_id属性即可,同时为了绘制,我们还需要保留文本的位置信息)。
3. 修改绘制部分:原代码绘制的是矩形框和类别标签,现在OCR返回的是关键点(四个点)和文本内容,我们可以用绘制关键点的方式(或者绘制多边形)来绘制文本框,并绘制识别到的字符串。
但是,原代码1中,检测结果对象(obj)具有x, y, w, h属性,而OCR返回的是四个点的坐标(obj.box,包含四个点)。为了兼容原绘制代码(draw_rect需要矩形框),我们可以计算四个点的最小外接矩形(x, y, w, h)来模拟。
然而,原handle_detection函数中,除了使用class_id,并没有使用其他属性(如位置信息)。所以我们可以只关注class_id,但绘制时需要使用位置信息。
因此,我们需要创建一个新的对象类型来模拟原YOLOv5的检测结果,这个对象需要有以下属性:
class_id: 映射后的整数(1-7或19)
x, y, w, h: 由OCR返回的四个点计算出的最小外接矩形
score: 置信度(OCR返回的obj.score)
另外,原代码1中有一个labels属性(detector.labels)用于获取类别名称,但现在我们不需要了,因为绘制时我们直接绘制OCR识别到的字符串(obj.char_str())即可。但为了不报错,我们可以给detector对象添加一个labels属性,或者修改绘制部分。
考虑到时间,我们修改绘制部分:不再使用detector.labels,而是直接使用OCR识别到的字符串(即原obj.char_str())。
具体修改计划:
1. 模型加载部分:
将原来的detector改为加载OCR模型,并调整相机分辨率(使用OCR模型要求的输入分辨率)。
2. 修改OperationController类中的handle_detection函数:它现在接收的是OCR识别结果(经过映射后的对象列表)和图像。
3. 主循环中:
- 读取图像
- 使用OCR模型检测
- 将每个OCR结果映射为模拟的检测对象(具有class_id, x, y, w, h, score, 以及text属性用于绘制)
- 调用controller.handle_detection(模拟的对象列表, img)
- 绘制:遍历模拟的对象列表,绘制矩形框(使用计算出的矩形)和文本(使用OCR识别到的原始字符串,而不是映射后的class_id)
4. 注意:原代码1中,当没有检测到物体时,不进行处理。现在,当没有OCR结果时,同样不处理。
5. 由于OCR模型可能识别到非数字的文本,我们需要过滤掉非目标文本(即不是01-08的文本)。
6. 模拟检测对象类定义(简单起见,使用namedtuple或自定义类)。
考虑到代码复杂度,我们创建一个简单的类来模拟:
class SimulatedObj:
def __init__(self, class_id, x, y, w, h, score, text):
self.class_id = class_id
self.x = x
self.y = y
self.w = w
self.h = h
self.score = score
self.text = text # 原始字符串,用于绘制
如何从OCR的一个结果obj(来自ocr.detect)计算矩形框?
OCR返回的obj有一个box属性,它是一个Box对象(根据代码2,它有to_list方法,返回四个点的列表,每个点是一个(x,y))。我们可以通过四个点计算最小外接矩形(即能包围这四个点的最小矩形)。
计算最小外接矩形的方法:
x_min = min(x1, x2, x3, x4)
y_min = min(y1, y2, y3, y4)
x_max = max(x1, x2, x3, x4)
y_max = max(y1, y2, y3, y4)
则矩形框的左上角坐标为(x_min, y_min),宽度为x_max-x_min,高度为y_max-y_min。
7. 映射规则:
对于OCR识别到的每个文本obj,先获取其字符串text = obj.char_str()。
如果text在["01","02","03","04","05","06","07"]中,则class_id = int(text)
如果text == "08",则class_id = 19
其他文本则忽略(不加入模拟对象列表)。
8. 注意:原代码1中,YOLOv5的检测结果有多个对象时,会遍历处理,但状态机可能只处理第一个符合条件的对象。现在,我们同样只处理第一个符合条件的OCR结果(因为状态机设计如此,避免多次触发)。但是,我们仍然需要绘制所有识别到的目标。
因此,在构建模拟对象列表时,我们不过滤,只是将非目标文本忽略(不加入列表)。然后,在handle_detection中,我们遍历模拟对象列表,只要遇到一个符合条件的就触发状态转换(并break)。
9. 相机分辨率调整:在代码2中,创建相机时使用了ocr.input_width()和ocr.input_height(),我们也需要这样设置。
10. 修改日志和错误处理。
由于代码较长,我们只修改关键部分,并尽量保留原结构。
开始修改代码1:</think>我将修改代码1,将YOLOv5模型识别逻辑替换为OCR模型识别逻辑,并按照要求处理识别结果。以下是修改后的代码:
```python
import logging
from maix import camera, display, image, nn, app, uart, time
import requests
import json
import os
import threading
from datetime import datetime
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler("/root/operation.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger("MAIX_PRO")
# 状态定义
class SystemState:
NORMAL = 0 # 正常检测模式
OBJECT_DETECTED = 1 # 物体检测处理中
SPECIAL_HANDLING = 2# 特殊处理模式(标签08)
WAIT_FOR_LABEL1 = 3 # 等待标签1状态
# OCR模型加载
try:
ocr = nn.PP_OCR(model="/root/models/pp_ocr.mud")
logger.info("PP_OCR model loaded")
except Exception as e:
logger.critical(f"OCR model load failed: {str(e)}")
exit(1)
# 保存目录
SAVE_DIR = "/boot/Pictures/"
os.makedirs(SAVE_DIR, exist_ok=True)
# 硬件初始化(使用OCR模型要求的分辨率)
try:
cam = camera.Camera(ocr.input_width(), ocr.input_height(), ocr.input_format())
logger.debug(f"Camera resolution: {cam.width()}x{cam.height()}")
except RuntimeError as e:
logger.critical(f"Camera init failed: {str(e)}")
exit(1)
disp = display.Display()
# UART初始化
device = "/dev/ttyS0"
serial0 = uart.UART(device, 38400)
logger.info("UART initialized")
# 登录获取token
login_url = "http://111.230.114.23/api/user/login"
headers_login = {'Content-Type': 'application/json'}
login_data = {"userAccount": "lanyating", "userPassword": 12345678}
json_data = json.dumps(login_data)
try:
login_response = requests.post(login_url, data=json_data, headers=headers_login)
response_json = login_response.json()
token = response_json.get('data')
if token:
logger.info(f"Login successful, token obtained: {token[:10]}...") # 只显示部分token以保护隐私
else:
logger.error(f"Login failed: No token returned in response")
exit(1)
except Exception as e:
logger.critical(f"Login failed: {str(e)}")
exit(1)
def send_uart(data):
try:
serial0.write_str(data + "\n")
logger.info(f"UART sent: {data}")
except Exception as e:
logger.error(f"UART send failed: {str(e)}")
class OperationController:
def __init__(self):
self.state = SystemState.NORMAL
self.current_label = None
self.last_detect_time = 0
self.upload_complete = False
self.lock = threading.Lock()
self.timers = []
# 初始发送forward
self.send_uart("forward")
# 初始化 photo_url 和 data_url
self.photo_url = "http://111.230.114.23/api/file/upload"
self.data_url = "http://111.230.114.23/api/data/add"
# 确保 token 在整个类中可用
self.token = token
def send_uart(self, command):
try:
serial0.write_str(command + "\n")
logger.info(f"UART sent: {command}")
except Exception as e:
logger.error(f"UART send failed: {str(e)}")
def save_and_upload(self, img, label):
try:
# 生成文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{SAVE_DIR}{label}_{timestamp}.jpg"
# 保存图片
if img.save(filename, quality=90):
logger.info(f"Image saved: {filename}")
# 同步上传
with open(filename, 'rb') as file:
files = {
'file': ('image.jpg', file, 'image/jpeg')
}
params = {
'biz': 'plant_picture',
}
headers = {
"token": self.token
}
logger.info(f"Uploading {filename} with label {label}, Token: {self.token[:10]}...")
response = requests.post(
self.photo_url,
files=files,
headers=headers,
params=params
)
if response.json().get('code') == 0 :
logger.info(f"Upload success: {filename}, Response: {response.text}")
return response.json().get('data')
else:
logger.warning(f"Upload failed: {response.text}")
else:
logger.error("Image save failed")
except Exception as e:
logger.error(f"Capture failed: {str(e)}")
return None
def save_data(self, data):
try:
params = [{
"deviceName": 1,
"plantId": 1,
"growthStage": "flowering",
"healthStage": "healthy",
"height": "5",
"crownWidth": "5",
"humidity": '',
"ph": '',
"dan": '',
"lin": '',
"jia": '',
"photoUrl": data,
"notes": ""
}]
headers = {
"token": self.token
}
response = requests.post(
self.data_url,
headers=headers,
json=params
)
logger.info(f"Response: {data}")
if response.json().get('code') == 0 :
logger.info(f"Data save success: {response.text}")
else:
logger.warning(f"Data save failed: {response.text}")
except Exception as e:
logger.error(f"Data upload error: {str(e)}")
def handle_detection(self, objs, img):
with self.lock:
current_time = time.time()
# 状态机逻辑
if self.state == SystemState.NORMAL:
for obj in objs:
text = obj.char_str().strip()
# 处理特殊标签08
if text == "08":
logger.info("Special label 08 detected")
self.state = SystemState.SPECIAL_HANDLING
self.send_uart("stop")
# 1秒后保存并上传
def delayed_save():
print("开始上传")
data = self.save_and_upload(img, 8) # 使用8代表08
print("上传成功")
if data:
self.save_data(data)
self.send_uart("back")
self.add_timer(1.0, delayed_save)
break
# 处理普通标签01-07
elif text in ["01", "02", "03", "04", "05", "06", "07"]:
label_id = int(text) # 转换为数字
logger.info(f"Label {text} detected")
self.state = SystemState.OBJECT_DETECTED
self.send_uart("stop")
# 1秒后保存并上传
def delayed_save():
print(f"开始上传标签{label_id}")
data = self.save_and_upload(img, label_id)
print("上传成功")
if data:
self.save_data(data)
self.add_timer(1.0, delayed_save)
# 2秒后发送forward
def delayed_forward():
self.send_uart("forward")
self.state = SystemState.NORMAL
self.add_timer(2.0, delayed_forward)
break
elif self.state == SystemState.SPECIAL_HANDLING:
# 等待上传完成
pass # 在同步上传的情况下不需要等待标志位
elif self.state == SystemState.WAIT_FOR_LABEL1:
for obj in objs:
if obj.char_str().strip() == "01":
logger.info("Label01 after special handling")
self.send_uart("stop")
self.add_timer(1.0, lambda: self.send_uart("forward"))
self.state = SystemState.NORMAL
break
def add_timer(self, delay, callback):
timer = threading.Timer(delay, callback)
timer.start()
self.timers.append(timer)
def cleanup(self):
for timer in self.timers:
timer.cancel()
logger.info("System cleanup completed")
# 主控制实例
controller = OperationController()
# 创建 Color 对象
red_color = image.Color(255, 0, 0) # 定义标准红色
# 主循环
try:
while not app.need_exit():
try:
img = cam.read()
except Exception as e:
logger.error(f"摄像头读取失败: {str(e)}")
continue
# 执行OCR检测
try:
ocr_objs = ocr.detect(img)
except Exception as e:
logger.error(f"OCR识别失败: {str(e)}")
disp.show(img)
continue
# 处理结果
if len(ocr_objs) > 0:
controller.handle_detection(ocr_objs, img)
# 显示画面 - 绘制OCR结果
for obj in ocr_objs:
# 绘制文本框(四个关键点)
points = obj.box.to_list()
img.draw_keypoints(points, image.COLOR_RED, 4, -1, 1)
# 绘制识别文本
text = obj.char_str()
img.draw_string(
obj.box.x4, # 使用右下角坐标
obj.box.y4,
text,
scale=1.0,
color=red_color
)
disp.show(img)
except KeyboardInterrupt:
logger.info("用户中断")
except Exception as e:
logger.critical(f"致命错误: {str(e)}")
finally:
controller.cleanup()
logger.info("系统关闭")
```
### 修改说明:
1. **模型替换**:
- 将YOLOv5模型替换为PP_OCR模型
- 调整相机分辨率以匹配OCR模型输入要求
2. **识别逻辑修改**:
- 当OCR识别到"01"-"07"时,执行原普通标签逻辑(`1 <= obj.class_id <= 18`)
- 当OCR识别到"08"时,执行原特殊标签逻辑(`obj.class_id == 19`)
- 添加文本清理(`strip()`)处理可能的空格问题
3. **结果处理**:
- 将文本标签转换为数字ID(01→1,08→8)
- 保留OCR原始文本用于显示
- 修改文件名生成使用数字ID而非文本
4. **绘制逻辑**:
- 使用`draw_keypoints`绘制OCR文本框(四个点)
- 在文本框右下角显示识别文本
- 移除原YOLOv5的矩形框绘制
5. **状态机调整**:
- 修改状态描述(SPECIAL_HANDLING对应08)
- 更新日志信息反映OCR识别结果
6. **错误处理**:
- 更新模型加载错误信息
- 修改OCR识别失败的错误日志
### 关键修改点:
1. OCR结果处理:
```python
for obj in objs:
text = obj.char_str().strip()
if text == "08": # 特殊处理
# 执行特殊标签逻辑
elif text in ["01", "02", "03", "04", "05", "06", "07"]: # 普通处理
label_id = int(text) # 转换为数字ID
# 执行普通标签逻辑
```
2. 绘制OCR结果:
```python
# 绘制文本框
points = obj.box.to_list()
img.draw_keypoints(points, image.COLOR_RED, 4, -1, 1)
# 绘制识别文本
img.draw_string(obj.box.x4, obj.box.y4, text, scale=1.0, color=red_color)
```
3. 文件名生成:
```python
# 使用数字ID而非文本标签
filename = f"{SAVE_DIR}{label}_{timestamp}.jpg"
```