Pythonでグラフ画像から数値を読み取ってエクセルに出力する
シーンとしては、例えば、製品Aと製品Bの性能を比較するときに特性図(グラフ)を参照することがある。カタログやWebにグラフは掲載されているが、画像になっているので重ね合わせて比較することができず、なんとなくAの方が優れてるかな?なんてあいまいな感じに終わってしまう。メーカーに問い合わせても数値を提供してくれることは稀。まぁそりゃそうだろう。
ということで、画像になっているグラフから数値を読み取って、それをエクセルに出力するプログラムを作った。
結果
参考データとしては十分に使えるレベルで数値化できたと思う。画像処理の膨張縮小、線の検出で位置ズレが起きているはずなのであくまで参考データ。
左が入力画像、右が出力結果。数値はエクセルに出力するがグラフ化は手作業で。
注意事項としては、
- 第一象限のグラフしか対応していない。(マイナスを含むデータは非対応)
- 対数グラフは対応していない。
- 入力するグラフ画像は補助線よりもデータの線が太い必要がある。画像処理で消せなくなる。
- 入力するグラフ画像の淵はペイントなどで消す必要あり。上記の左画像の元データは以下。
プログラム
相変わらず、センスのなさに歯がゆい思いがする…まぁ動くのでよしとするが。
# 29_ExtractChart_001.py # python 3.8.1 # opencv-python 4.1.2.30 # coding: utf-8 # import cv2 import numpy as np import datetime import openpyxl #===================# # Define parameters # #===================# image = "f.png" k = 2 th = 100 y_max = 1 x_min = 350 x_max = 750 #=================# # Define function # #=================# # Function for extracting XY data from an imaged chart. def get_profile(): ## Add closing and threshold to erase auxiliary lines on the chart. img = cv2.imread(image, 0) kernel = np.ones((k, k), np.uint8) ret,img = cv2.threshold(img, th, 255, cv2.THRESH_BINARY) img = cv2.dilate(img, kernel,iterations = 1) img = cv2.erode(img, kernel,iterations = 1) # img = cv2.dilate(img, kernel,iterations = 10) # img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) cv2.imshow("output", img) ## Get num of pixels of img. h:height w:width h, w = img.shape ## Array for XY data. [X, Y]. profile = [] ## print(type(profie)) -> <class 'list'> Not nparray! ## Get each XY-coordinate of each points on the line of the chart. for i in range(w): line = img[:,i] edge = [j for j in range(h-1) if line[j] != line[j+1]] ##Seach two inflection points (W to B and B to W) on a vertical line. if len(edge) < 1: val = 0 elif len(edge) == 1: if edge[0] > h/2: val = 0 else: val = h else: val = h - sum(edge)/2 ## Reverse Y-coordinate(top to bottom -> bottom to top). point = [i+1, val] ## XY data [X, Y] profile.append(point) ## Do unit conversion on X and Y coordinate. y = max(profile, key= lambda p:p[1])[1] ##Get maximum value of Y data. for q in range(w): profile[q][0] = profile[q][0] * (x_max - x_min) / w + x_min profile[q][1] = profile[q][1] * y_max / y return profile # Export 2Darray to xlsx format. def export_xlsx(arry): wb = openpyxl.Workbook() ws = wb.create_sheet(index= 0, title = "Line profile") ws = wb["Line profile"] ## I don't know how this works. wb.active = wb.sheetnames.index("Line profile") row = 1 for i in arry: celA = "A" + str(row) celB = "B" + str(row) ws[celA] = i[0] ws[celB] = i[1] row += 1 try: now = datetime.datetime.now() wb.save('LineProfile_{0:%Y%m%d%H%M%S}.xlsx'.format(now)) print("Save completed") except: print("Save failed!") #======# # Main # #======# if __name__ == "__main__": export_xlsx(get_profile())