我们在数据分析工作中,经常遇到没有直接数据的情况,对于曲线图情况,我们需要解析曲线图中的数据。
例如下图,根据文档我们获知横坐标取值范围为(0,175),纵坐标取值范围(0,156)。如何把曲线转换为可操作的数据呢,具体步骤如下:
- 用画图工具抠图,按曲线图坐标边界抠出待处理图形,例如windows系统上的“画图”功能即可;
- 使用OpenCV提供的API,详见“Opencv-python去图标与水印方案实践”,采用直接提取曲线的方案;
- 使用OpenCV提供的图像灰度处理及二值化API,得到目标图;
- 解析二值化的灰度图像得出数据。
1. 用画图工具抠图
我们把抠出来的图,另存为:heart1.JPG。
2. OpenCV直接提取曲线的方案
import numpy as np
import matplotlib.pyplot as plt
import cv2
img=cv2.imread('img\heart1.JPG')h,w,l=img.shapefor j in range(h):for k in range(w):if img[j][k][0] <135 or img[j][k][0] >175 or img[j][k][1] <185 or img[j][k][1] >220 or img[j][k][2] <45 or img[j][k][2] >129:img[j][k][0] = 255img[j][k][1] = 255 img[j][k][2] = 255plt.imshow(img,cmap=plt.cm.gray)
其中,提取颜色范围值,请参照文档“Opencv-python去图标与水印方案实践”。
3. 使用OpenCV图像灰度处理及二值化API
image_gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) # 转换成灰度图
plt.imshow(image_gray,cmap=plt.cm.gray)
# 二值化
thresh, new_img = cv2.threshold(image_gray, 200, 255, cv2.THRESH_BINARY)cv2.imshow('NEW_IMG', new_img)
cv2.waitKey()
new_img = new_img.astype(np.int16)
注:图像数据格式是uint8,最大值255,需要转换为int16,存储实际坐标值。
OpenCV提供的图像二值化API,threshold()方法参数:
- 图片矩阵
- 阈值
- 图片中的最大值
- 二值化的方式
二值化的方式:
THRESH_BINARY | 高于阈值改为255,低于阈值改为0 |
---|---|
THRESH_BINARY_INV | 高于阈值改为0,低于阈值改为255 |
THRESH_TRUNC | 截断,高于阈值改为阈值,最大值失效 |
THRESH_TOZERO | 高于阈值不改变,低于阈值改为0 |
THRESH_TOZERO_INV | 高于阈值该为0,低于阈值不改变 |
4. 解析二值化的灰度图像得出数据
解析数据的原理:
首先,明确坐标系统,图像数据(二维),从(0,0)开始对应实际图形(0,h),其中h是最大高度,可以理解为纵向坐标倒序。
其次,解析图数据,对于图中曲线黑色,数值为0,直接标记为纵轴的坐标(注意倒序),其他设置为0,由此获得二维矩阵待处理。
接着,按坐标均值定义为目标数据。
r,c = new_img.shape
for i in range(r):for j in range(c):if new_img[i][j]==255:new_img[i][j]=0else:new_img[i][j]=280 - i + 1dat = new_img.mean(axis=0, keepdims=False,where=new_img>0)#滤除手工抠图边界无数据情况
mask = np.isnan(dat)
dat = np.delete(dat, np.where(mask))
dat = dat*156/np.max(dat)
# 解析出数据,未进行度量单位转换
new_dat = dat.astype(np.int16)# 绘图回放数据
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置正常显示中文
plt.rcParams['axes.unicode_minus']=False # 解决不显示负号
plt.figure(figsize=(12,6))
plt.xlim(0,570)
x_lable = ['0','30','60','90','120','150','180']
pos_list = [0, 94, 188, 281, 375, 469,563]plt.xticks(pos_list, x_lable)
#ax = plt.axes()
#ax.xaxis.set_major_locator(ticker.FixedLocator((name_list)))
#ax.xaxis.set_major_formatter(ticker.FixedFormatter((x_lable)))
plt.ylim(0,180)
plt.plot(new_dat)
plt.ylabel("心率")plt.show()
绘图回放数据,结果如下:
5. 总结
解析曲线数据图还是比较麻烦,首先是图像处理技术,我们常见的曲线图一般都较为干净,好处理些。在解析过程中,需要进行坐标转换,也就是数据的度量单位。
欢迎交流更多的方法。
参考:
肖永威. Opencv-python去图标与水印方案实践. CSDN博客. 2023.09