tesseract + pillow 提取电费单曲线数据

最近在做储能项目时需要用户提供负荷数据,大部分客户提供的都是 excel 或者 pdf 格式的文件。但是这次遇到一个客户因为特殊原因无法提供可读文件,只给我们提供了一年 365 天每天的负荷曲线图片,其中某一天的图片如下

所以我们要通过技术手段提取折线图中的每个小时所对应的数据,初步思路如下:

  1. 使用 ocr 提取最大值 max 和最小值 min;
  2. 截取曲线图部分,利用 pillow 读取每个像素点的颜色,然后找出曲线;
  3. 根据曲线图宽度 / 24,得到整点的分段,然后去匹配对应的 y 轴的位置;
  4. 结合 min、max、y 计算得到具体的数值 y1;

使用 tesseract 识别出 min 和 max

环境准备:

makefile 复制代码
python: v3.11.6
pytesseract: 0.3.10
pillow: 10.1.0

根据坐标裁剪图片,只保留最大值、最小值部分,去除干扰,大致代码如下

python 复制代码
from PIL import Image

image = Image.open("./images/4.png").convert("RGB")

width, height = image.size

crop_area = (368, 96, 675, 110)
cropped_image = image.crop(crop_area)

cropped_image.show()

于是就得到了下面的图片

接着对图片进行二值化和放大处理,更利于提高 ocr 的准确率

python 复制代码
gray_image = cropped_image.convert("L")
gray_image = gray_image.resize((gray_image.width * 3, gray_image.height * 3))

gray_image.show()

尝试使用 pytesseract ocr 图片内容

python 复制代码
text = pytesseract.image_to_string(
	gray_image,
	lang="eng",
	config="--psm 7 --oem 3 -c tessedit_char_whitelist='0123456789.: '",
)

print(text)

输出如下:

编写提取 min、max 的分割逻辑

python 复制代码
# 对 text 按空格分隔, 并过滤掉空字符串
text = text.replace("\n", "").replace(":", " ").split(" ")
text = list(filter(lambda x: "." in x, text))

min = 0
max = 0

if len(text) == 2:
	min = float(text[0])
	max = float(text[1])
elif len(text) == 1:
	max = float(text[0])

print("min: {}, max: {}".format(min, max))

输出如下:

基于像素色值提取曲线

先对图片进行裁剪,截取出曲线区域,为了后续计算位置

python 复制代码
chart_area = (70, 158, width - 28, height - 77)
chart_image = image.crop(chart_area)

chart_image.show();

曲线颜色大致是红色,可以遍历像素点,将红色的点修改为绿色,然后观察是否找对位置

python 复制代码
color_to_change = (255, 0, 0)
target_color = (0, 255, 0)

pixels = chart_image.load()
width, height = chart_image.size

# 因为图片质量问题,其实曲线并不是红色,也不是一个颜色,经过一番调试,得到了以下的代码
for x in range(width):
    for y in range(height):
        if (
            abs(pixels[x, y][0] - color_to_change[0]) < 30
            and abs(pixels[x, y][1] - color_to_change[1]) < 180
            and abs(pixels[x, y][2] - color_to_change[2]) < 180
        ):
            pixels[x, y] = target_color           

chart_image.show()

运行一下,可以得到下面的结果

我们在之前已经获取到了曲线的 min、max,为了后续计算,我们还需要知道 min、max 值出现的位置,对上面的代码进行改造

python 复制代码
minXPoint = ()
maxXPoint = ()
minYPoint = ()
maxYPoint = ()

pixels = chart_image.load()
width, height = chart_image.size

points = []

for x in range(width):
    for y in range(height):
        if (
            abs(pixels[x, y][0] - color_to_change[0]) < 30
            and abs(pixels[x, y][1] - color_to_change[1]) < 180
            and abs(pixels[x, y][2] - color_to_change[2]) < 180
        ):
            pixels[x, y] = target_color

            if len(minXPoint) > 0:
                minXPoint = (x, y) if minXPoint[0] > x else minXPoint
            else:
                minXPoint = (x, y)

            if len(maxXPoint) > 0:
                maxXPoint = (x, y) if maxXPoint[0] < x else maxXPoint
            else:
                maxXPoint = (x, y)

            if len(minYPoint) > 0:
                minYPoint = (x, y) if minYPoint[1] > y else minYPoint
            else:
                minYPoint = (x, y)

            if len(maxYPoint) > 0:
                maxYPoint = (x, y) if maxYPoint[1] < y else maxYPoint
            else:
                maxYPoint = (x, y)

            points.append((x, y))

print("minXPoint: ", minXPoint, "maxPoint: ", maxXPoint, "minYPoint: ", minYPoint, "maxYPoint: ", maxYPoint)

执行结果如下,我们获取到了 x 和 y 最大、最小值的坐标

分割x轴并计算

python 复制代码
interval = width / 24
start = round(minXPoint[0] / interval)
end = 24

# 用于存放 24 小时的数据坐标
points24 = [0 * i for i in range(0, 24)]

# 根据 x 在 points 中尝试寻找 y 的值,如果有多个,取第一个
for i in range(start, end):
    x = round(i * interval)
    p = [tup for tup in points if tup[0] == x]
    points24[i] = p[0] if len(p) > 0 else 0

# 每一像素代表多少
yInterval = (max - min) / (maxYPoint[1] - minYPoint[1])

for i in range(len(points24)):
    p = points24[i]
    if p == 0:
        continue
    y = format((maxYPoint[1] - p[1]) * yInterval + min, ".2f")
    print("{}:00, {}".format(i, y))

运行一下,结果如下

至此,我们就提取出了曲线上的整点的数据。

最后一步

读取 images 下的所有文件,然后 for loop 一下,将输出结构化合并整理成 excel 就 👌 了,基操没啥难度,就不写了。

相关推荐
程序猿000001号9 分钟前
探索Python的pytest库:简化单元测试的艺术
python·单元测试·pytest
engchina40 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
Dream_Snowar2 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶2 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
汪洪墩2 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
程序员shen1616113 小时前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
人人人人一样一样4 小时前
作业Python
python
四口鲸鱼爱吃盐4 小时前
Pytorch | 利用VMI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
四口鲸鱼爱吃盐4 小时前
Pytorch | 利用PI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python