<think>我们面临的任务是识别水表上的数字读数以及四个红色小指针的读数。水表通常包含一个数字计数器(通常是最后一位为小数)和四个指针式表盘(分别代表0.1、0.01、0.001、0.0001立方米)。由于指针是红色的,我们可以利用颜色分割来提取指针区域,然后通过计算指针角度来确定读数。
步骤:
1. 读取图像,进行预处理(如去噪、调整大小等)。
2. 识别数字部分:通常水表数字部分位于表盘上方,我们可以通过定位数字区域,然后使用OCR(如Tesseract)识别数字。
3. 识别四个红色指针:
a. 颜色分割:利用HSV颜色空间提取红色区域。
b. 形态学操作:去除噪声,连接相邻区域。
c. 轮廓检测:找到指针的轮廓。
d. 确定指针角度:通过拟合直线或最小外接矩形计算指针角度。
e. 根据角度转换为读数(每个表盘有0-10的刻度,指针角度对应0-9的读数)。
注意:四个指针表盘通常是同心圆排列或线性排列,我们需要定位每个表盘的中心和刻度。
由于水表样式多样,这里假设一种常见布局:数字部分在上方,四个圆形表盘在下方呈一字排列。
我们将分两部分实现:
第一部分:数字识别
第二部分:指针识别
代码实现:
</think>
# 水表数字与指针读数识别方案
要识别水表上的数字读数及四个红色指针的读数,我们需要结合OCR技术和图像处理技术。以下是完整的解决方案:
```python
import cv2
import numpy as np
import pytesseract
import math
import matplotlib.pyplot as plt
# 配置Tesseract路径(根据您的系统修改)
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
def preprocess_image(image_path):
"""
图像预处理:调整大小、去噪、增强对比度
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
# 调整大小
img = cv2.resize(img, (800, 600))
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 中值滤波去噪
denoised = cv2.medianBlur(gray, 3)
# 直方图均衡化增强对比度
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
enhanced = clahe.apply(denoised)
return img, enhanced
def detect_digits_region(img, enhanced):
"""
检测数字区域并识别数字
"""
# 二值化
_, binary = cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 形态学操作(闭运算填充小孔)
kernel = np.ones((3, 3), np.uint8)
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# 查找轮廓
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 筛选可能是数字区域的轮廓(基于宽高比和面积)
digit_contours = []
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = w / float(h)
area = cv2.contourArea(cnt)
# 筛选条件(根据实际水表调整)
if 0.2 < aspect_ratio < 1.2 and 500 < area < 5000:
digit_contours.append((x, y, w, h))
# 如果没有找到数字区域,尝试使用固定位置
if not digit_contours:
# 假设数字区域在图像顶部1/3部分
h, w = enhanced.shape
roi = enhanced[0:h//3, 0:w]
digits = recognize_digits(roi)
return digits, None
# 找到最大的数字区域(假设只有一个数字区域)
digit_contours.sort(key=lambda rect: rect[2]*rect[3], reverse=True)
x, y, w, h = digit_contours[0]
# 提取数字区域
digit_roi = enhanced[y:y+h, x:x+w]
# 识别数字
digits = recognize_digits(digit_roi)
return digits, (x, y, w, h)
def recognize_digits(roi):
"""
识别数字区域中的数字
"""
# 二值化
_, binary = cv2.threshold(roi, 0, 255, cv2.RESIZE_AREA)
# 调整大小以改善OCR识别
binary = cv2.resize(binary, (w*2, h*2), interpolation=cv2.INTER_CUBIC)
# 使用Tesseract识别数字
custom_config = r'--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789.'
text = pytesseract.image_to_string(binary, config=custom_config)
# 清理结果:只保留数字和小数点
cleaned = ''.join(filter(lambda c: c.isdigit() or c == '.', text))
return cleaned
def detect_red_pointers(img):
"""
检测红色指针并计算其角度
"""
# 转换为HSV颜色空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 定义红色的HSV范围(红色在HSV中有两个范围)
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([180, 255, 255])
# 创建红色掩码
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
red_mask = cv2.bitwise_or(mask1, mask2)
# 形态学操作去除噪点
kernel = np.ones((5, 5), np.uint8)
red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel)
red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_CLOSE, kernel)
# 查找轮廓
contours, _ = cv2.findContours(red_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 筛选指针轮廓(基于面积和形状)
pointer_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
if 50 < area < 500: # 指针面积范围
# 计算轮廓的凸包
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
# 计算轮廓的圆形度
circularity = 4 * np.pi * area / (cv2.arcLength(cnt, True) ** 2)
# 筛选条件(指针通常不是圆形)
if circularity < 0.5:
pointer_contours.append(cnt)
# 按位置排序指针(从左到右)
pointer_contours.sort(key=lambda c: cv2.boundingRect(c)[0])
# 限制最多只取4个指针(水表通常有4个小指针)
if len(pointer_contours) > 4:
pointer_contours = pointer_contours[:4]
return pointer_contours
def calculate_pointer_angles(img, pointers):
"""
计算每个指针的角度(0-360度)
"""
angles = []
pointer_positions = []
for i, cnt in enumerate(pointers):
# 计算最小外接矩形
rect = cv2.minAreaRect(cnt)
center, size, angle = rect
box = cv2.boxPoints(rect)
box = np.int0(box)
# 计算指针方向(使用PCA分析)
data_pts = cnt.reshape(-1, 2).astype(np.float32)
mean = np.empty((0))
mean, eigenvectors, eigenvalues = cv2.PCACompute2(data_pts, mean)
# 计算指针角度(相对于垂直方向)
angle_rad = math.atan2(eigenvectors[0, 1], eigenvectors[0, 0])
angle_deg = np.degrees(angle_rad)
# 调整角度范围到0-360度
if angle_deg < 0:
angle_deg += 360
angles.append(angle_deg)
pointer_positions.append(center)
return angles, pointer_positions
def calculate_pointer_readings(angles):
"""
根据指针角度计算读数(0-9)
"""
readings = []
for angle in angles:
# 调整角度(水表通常0刻度在顶部)
adjusted_angle = (angle + 90) % 360
# 计算读数(每个刻度36度)
reading = adjusted_angle / 36.0
# 取整数部分,四舍五入到最近的整数
reading = round(reading) % 10
readings.append(reading)
return readings
def visualize_results(img, digit_region, pointers, angles, pointer_readings):
"""
可视化结果
"""
# 创建结果图像副本
result_img = img.copy()
# 绘制数字区域
if digit_region:
x, y, w, h = digit_region
cv2.rectangle(result_img, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 绘制指针和角度
for i, (center, angle) in enumerate(zip(pointers, angles)):
# 绘制指针轮廓
cv2.drawContours(result_img, [pointers[i]], -1, (255, 0, 0), 2)
# 绘制中心点
cv2.circle(result_img, (int(center[0]), int(center[1])), 5, (0, 0, 255), -1)
# 绘制角度线
end_x = int(center[0] + 50 * math.cos(math.radians(angle)))
end_y = int(center[1] + 50 * math.sin(math.radians(angle)))
cv2.line(result_img, (int(center[0]), int(center[1])), (end_x, end_y), (0, 255, 255), 2)
# 添加读数文本
text = f"{pointer_readings[i]}"
cv2.putText(result_img, text, (int(center[0]) - 10, int(center[1]) - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
return result_img
def read_water_meter(image_path):
"""
主函数:读取水表图像并返回读数
"""
# 1. 预处理图像
img, enhanced = preprocess_image(image_path)
# 2. 检测并识别数字区域
digit_reading, digit_region = detect_digits_region(img, enhanced)
# 3. 检测红色指针
pointers = detect_red_pointers(img)
# 4. 计算指针角度
angles, pointer_positions = calculate_pointer_angles(img, pointers)
# 5. 计算指针读数
pointer_readings = calculate_pointer_readings(angles)
# 6. 可视化结果
result_img = visualize_results(img, digit_region, pointers, angles, pointer_readings)
# 7. 组合结果
result = {
"digit_reading": digit_reading,
"pointer_readings": pointer_readings,
"visualization": result_img
}
return result
if __name__ == "__main__":
# 使用示例
try:
# 替换为您的图像路径
image_path = "water_meter.jpg"
# 读取水表
result = read_water_meter(image_path)
# 打印结果
print(f"数字读数: {result['digit_reading']}")
print(f"指针读数: {result['pointer_readings']}")
# 显示可视化结果
cv2.imshow("Water Meter Reading", result["visualization"])
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果图像
cv2.imwrite("water_meter_result.jpg", result["visualization"])
except Exception as e:
print(f"错误: {str(e)}")
```
## 代码详细解释
### 1. 图像预处理 (`preprocess_image`)
- 调整图像大小至标准尺寸 (800x600)
- 转换为灰度图并应用中值滤波去噪
- 使用CLAHE算法增强对比度,提高特征可见性
### 2. 数字区域检测与识别 (`detect_digits_region`, `recognize_digits`)
- 使用二值化和形态学操作定位数字区域
- 基于轮廓特征(宽高比、面积)筛选数字区域
- 使用Tesseract OCR识别数字,只保留数字和小数点
### 3. 红色指针检测 (`detect_red_pointers`)
- 转换到HSV颜色空间,定义红色的HSV范围
- 创建红色掩码并进行形态学操作去除噪点
- 基于面积和形状(圆形度)筛选指针轮廓
- 按位置排序指针(从左到右)
### 4. 指针角度计算 (`calculate_pointer_angles`)
- 使用最小外接矩形获取指针方向
- 应用PCA(主成分分析)计算精确角度
- 将角度转换为0-360度范围
### 5. 指针读数计算 (`calculate_pointer_readings`)
- 调整角度参考系(水表0刻度通常在顶部)
- 将360度分为10等份(每36度一个刻度)
- 计算指针指向的刻度值(0-9)
### 6. 结果可视化 (`visualize_results`)
- 在原始图像上绘制数字区域边界框
- 标记指针中心点并绘制方向线
- 显示每个指针的读数
## 使用说明
1. **安装依赖**:
```bash
pip install opencv-python numpy pytesseract matplotlib
```
2. **配置Tesseract路径**:
在代码开头修改`pytesseract.pytesseract.tesseract_cmd`为您的Tesseract安装路径
3. **运行程序**:
```bash
python water_meter_reader.py
```
## 优化建议
1. **针对特定水表模型的优化**:
- 调整数字区域检测参数(面积、宽高比范围)
- 修改指针角度计算逻辑以适应不同的表盘布局
2. **提高OCR准确率**:
```python
# 在recognize_digits函数中添加预处理
def recognize_digits(roi):
# 添加锐化处理
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
sharpened = cv2.filter2D(roi, -1, kernel)
# 添加额外的二值化处理
_, binary = cv2.threshold(sharpened, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 其余代码不变...
```
3. **处理不同光照条件**:
```python
# 在preprocess_image中添加自适应直方图均衡化
def preprocess_image(image_path):
img = cv2.imread(image_path)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
# 自适应直方图均衡化
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
cl = clahe.apply(l)
# 合并通道
limg = cv2.merge((cl, a, b))
enhanced = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
return enhanced
```
4. **使用机器学习提高鲁棒性**:
- 训练CNN模型识别指针角度
- 使用目标检测模型定位数字区域