如何用TensorFlow与Python提取电表图像中的黑色数字区域?
用TensorFlow提取电表黑色数字区域的解决方案
我懂你碰到的这个棘手问题——之前用OpenCV固定阈值加findContours搞不定电表的黑色数字区域,毕竟光照不均、局部阴影这些情况一出现,固定阈值就彻底失效了。而电表刚好是白背景黑数字的明确场景,用TensorFlow来处理会更灵活精准,给你几个实用的方案:
方法一:自定义二分类CNN模型(适配你的专属场景)
如果能收集到一批电表照片,这个方法的效果会最贴合你的需求,毕竟是专门针对你的场景训练的模型:
- 准备数据集:
收集几十张不同光照、角度的电表照片,用标注工具(比如LabelMe)手动框选数字区域,生成对应的掩码图——数字区域标为1,背景标为0。把图像统一缩放到固定尺寸(比如256×256)。 - 构建简单的分割模型:
这个模型用CNN做下采样提取特征,再通过转置卷积恢复尺寸,最终输出二值掩码:import tensorflow as tf from tensorflow.keras import layers def build_digit_segmenter(input_shape=(256, 256, 3)): inputs = tf.keras.Input(shape=input_shape) # 下采样提取特征 x = layers.Conv2D(32, (3, 3), activation='relu', padding='same')(inputs) x = layers.MaxPooling2D((2, 2))(x) x = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(x) x = layers.MaxPooling2D((2, 2))(x) # 上采样恢复图像尺寸 x = layers.Conv2DTranspose(64, (3, 3), strides=(2, 2), padding='same', activation='relu')(x) x = layers.Conv2DTranspose(32, (3, 3), strides=(2, 2), padding='same', activation='relu')(x) # 输出二分类掩码(sigmoid激活输出0-1之间的概率) outputs = layers.Conv2D(1, (1, 1), activation='sigmoid')(x) model = tf.keras.Model(inputs=inputs, outputs=outputs) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) return model # 初始化模型 model = build_digit_segmenter() - 训练模型:
把标注好的图像和掩码分成训练集和验证集,开始训练:# 假设train_images(归一化到0-1)和train_masks是你的训练数据,形状分别为(None,256,256,3)和(None,256,256,1) model.fit(train_images, train_masks, epochs=20, batch_size=8, validation_split=0.2) - 提取数字区域:
用训练好的模型预测新图像,转成二值掩码后和原图像做位运算,就能分离出数字区域:import cv2 import numpy as np def extract_digits(image): # 调整图像尺寸到模型输入大小并归一化 resized_img = tf.image.resize(image, (256, 256)) / 255.0 resized_img = tf.expand_dims(resized_img, 0) # 预测掩码 pred_mask = model.predict(resized_img)[0] # 转成二值图(阈值设为0.5,概率大于0.5的判定为数字区域) binary_mask = (pred_mask > 0.5).astype(np.uint8) * 255 # 恢复原图像尺寸 binary_mask = tf.image.resize(binary_mask, (image.shape[0], image.shape[1])).numpy().astype(np.uint8) # 用掩码提取数字区域 digit_area = cv2.bitwise_and(image, image, mask=binary_mask.squeeze()) return digit_area
方法二:用预训练语义分割模型快速上手
如果没精力标注数据集,可以用TensorFlow Hub里的预训练分割模型(比如DeepLabV3+),这类模型已经学习过大量图像的特征,能快速应对复杂场景:
import tensorflow as tf import tensorflow_hub as hub import cv2 # 加载预训练语义分割模型 segmenter = hub.load("https://tfhub.dev/tensorflow/deeplabv3+/1") def get_digit_area_with_pretrained(image): # 模型输入要求:RGB图像,尺寸任意,归一化到0-1 input_tensor = tf.convert_to_tensor(image, dtype=tf.float32) / 255.0 input_tensor = input_tensor[tf.newaxis, ...] # 预测分割结果 result = segmenter(input_tensor) segmentation_mask = result['segmentation_mask'][0] # 因为电表数字是深色,我们可以筛选出像素值较低的区域(对应模型中的"dark object"类别) # 或者根据实际结果调整阈值,把数字区域单独提取出来 binary_mask = (segmentation_mask == 0).astype(np.uint8) * 255 # 这里类别编号需要根据实际测试调整 digit_area = cv2.bitwise_and(image, image, mask=binary_mask) return digit_area
注意: 预训练模型的类别编号需要你实际测试后调整,比如有些模型会把深色物体归为特定类别,你可以打印segmentation_mask的取值范围来找到对应数字的类别。
方法三:TensorFlow自适应阈值(无训练快速验证)
如果不想碰模型,也可以用TensorFlow实现自适应阈值,比OpenCV的固定阈值更能应对光照变化:
import tensorflow as tf import cv2 def adaptive_threshold_tf(image): # 转灰度图 gray = tf.image.rgb_to_grayscale(image) # 计算15×15窗口内的局部均值(窗口大小可以根据电表数字大小调整) kernel = tf.ones((15, 15), tf.float32) / (15*15) local_mean = tf.nn.conv2d(gray, kernel[..., tf.newaxis, tf.newaxis], strides=1, padding='SAME') # 自适应阈值:像素值比局部均值低5以上的判定为数字区域(黑色) thresholded = tf.where(gray < local_mean - 5, 255, 0) # 转成OpenCV能用的格式 binary_mask = thresholded.numpy().squeeze().astype(np.uint8) # 提取数字区域 digit_area = cv2.bitwise_and(image, image, mask=binary_mask) return digit_area
这个方法不需要训练,直接跑就能出结果,适合快速验证效果,窗口大小和阈值差可以根据你的电表照片调整。
内容的提问来源于stack exchange,提问作者Ignacio Mtz De Salinas Ureta
相关产品推荐
相关产品推荐

