OpenCVとTesseract-OCRで作る表のOCRツール rev1
2021/03/27 categories:OpenCV| tags:OpenCV|Python|Tesseract-OCR|
OpenCVとTesseract-OCRで作る表のOCRツールで作成したルーツを更新しました。主な変更点はコードの構造を書き換えたり、GUIを作り変えたといった内容です。PyQt5のコード作成に慣れてきて以前書いたコードを書き換えたくなったので変更しました。また、GUIが使いにくいと思っていたので変更をしました。
ファイルの追加と表示
OpenボタンをクリックしてPDFファイルを選択するとファイルリストにファイル名が表示されます。ファイル名をクリックするとPDFが画像に変換されて表示します。画像変換済みのファイルは2列目にimageと表示されます。imageと表示されているファイルをクリックした場合、再度画像変換することはなく、作成済みの画像を表示します。チェックボックスを切り替えると画像の表示と非表示が切り替えられます。
セルの認識
Recognize allをクリックして表のセルを認識します。認識結果はファイル名の子としてリストに追加されます。Rectはセルの認識結果で赤色で表示しているもの、Cropはセル内の余白をクロップした緑色で表示しているものです。その認識結果の2列目には認識結果に基づいてクロップした画像が表示されます。
ファイル名の子として表示されているアイテムをクリックすると、そのアイテムがどの場所であるか確認できるように青色で表示します。
セルの認識エリアの削除と追加
ファイル名の子として表示されているアイテムをクリックした状態で、Delete rectボタンをクリックすると、選択したアイテムを削除できます。また、その状態でDraw rectをクリックしてから画像内をドラッグして範囲指定すると、その範囲をクロップしてファイルアイテムに追加します。
セルのOCR
OCRボタンをクリックして2列目に表示されているセルの画像をすべて文字列に変換します。
CSVファイルの保存
文字列はSaveボタンをクリックするとCSVファイルとして保存できます。作成されるCSVファイルは以下の通りです。
ソースコード
main.py
ここをクリックして展開
# -*- coding: utf-8 -*-
import csv
import sys
from pathlib import Path
from poppler import Poppler
from PyQt5 import QtWidgets, QtCore, QtGui
from table_recognition_view import TableRecognitionView
from toolbar import ToolBar
from filelist import FileList
from tesseract_ocr import TesseractOCR
from iamge_processing import ImageProcessing
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.poppler = Poppler('./poppler/')
self.tesseractOCR = TesseractOCR('./Tesseract-OCR/tesseract.exe')
self.resize(800, 600)
self.setCentralWidget( QtWidgets.QWidget(self) )
self.recogView = TableRecognitionView( self.centralWidget() )
self.layout = QtWidgets.QVBoxLayout( self.centralWidget() )
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.recogView)
self.toolBar = ToolBar( self.centralWidget() )
self.toolBarDock = QtWidgets.QDockWidget('', self)
self.toolBarDock.setWidget(self.toolBar)
self.toolBarDock.setFloating(False)
self.toolBarDock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
self.toolBarDock.setTitleBarWidget( QtWidgets.QWidget() )
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.toolBarDock)
self.fileList = FileList(self)
self.fileListDock = QtWidgets.QDockWidget('File list', self)
self.fileListDock.setWidget(self.fileList)
self.fileListDock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
self.fileListDock.setTitleBarWidget( QtWidgets.QWidget() )
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.fileListDock)
self.toolBar.drawMenu.drawRectButton.clicked.connect(lambda flag : self.recogView.setDrawRectFlag(flag))
self.toolBar.drawMenu.deleteRectButton.clicked.connect(self.removeSelectedRects)
self.toolBar.drawMenu.drawRectButton.setEnabled(False)
self.toolBar.fileMenu.openButton.clicked.connect(self.fileOpen)
self.toolBar.fileMenu.saveButton.clicked.connect(self.saveFiles)
self.toolBar.imageMenu.recognizeButton.clicked.connect(self.recognizeAll)
self.toolBar.viewMenu.rowHeight.valueChanged.connect(self.rowHeightChanged)
self.toolBar.viewMenu.addToggleViewButton(self.fileListDock.toggleViewAction(), 0, 0, 1, 1)
self.toolBar.ocrMenu.ocrButton.clicked.connect(self.ocrChildren)
self.recogView.mouseLeftReleasedSignal.connect(self.recogViewClicked)
self.fileList.tableClicked.connect(self.filelistClicked)
self.rowHeightChanged( self.toolBar.viewMenu.rowHeight.value() )
def filelistClicked(self, clickedIndex):
if not clickedIndex.parent().isValid():
self.recogView.drawRectFlag = self.toolBar.drawMenu.drawRectButton.isChecked() and True
pixmap = self.fileList.item(clickedIndex.row(), 1).data()
if pixmap is None:
filePath = self.fileList.filePath( clickedIndex.row() )
pixmap = self.pathToPixmap( clickedIndex, filePath )
pixmapIndex = self.fileList.index(clickedIndex.row(), 1)
self.fileList.setData( pixmapIndex, 'image', pixmap )
parentIndex = clickedIndex
pixmap = self.fileList.getImage( parentIndex.row() )
self.fileList.view.setCurrentIndex(clickedIndex)
self.toolBar.drawMenu.drawRectButton.setEnabled(True)
else:
self.recogView.drawRectFlag = False
parentIndex = clickedIndex.parent()
pixmap = self.fileList.getImage( parentIndex.row() )
self.toolBar.drawMenu.drawRectButton.setEnabled(False)
self.refreshView(pixmap, parentIndex)
def fileOpen(self):
files, _ = QtWidgets.QFileDialog.getOpenFileNames(None, 'Open PDF files', '')
for path in [ Path(f) for f in files]:
self.fileList.appendFile(path)
self.fileList.setViewSize()
def ocrChildren(self):
count = 0
for row in range(self.fileList.model.rowCount()):
for rectRow in range(self.fileList.model.item(row).rowCount()):
count = count + 1
progressBar = QtWidgets.QProgressBar()
progressBar.setMaximum(count)
self.statusBar().addPermanentWidget(progressBar)
count = 0
for row in range(self.fileList.model.rowCount()):
fileItem = self.fileList.model.item(row)
for rectRow in range(fileItem.rowCount()):
rectPixmap = fileItem.child(rectRow, 1).data()
text = self.tesseractOCR.OCR(rectPixmap)
fileItem.child(rectRow, 2).setText(text)
count = count + 1
progressBar.setValue(count)
QtWidgets.QApplication.processEvents()
self.statusBar().removeWidget(progressBar)
def pathToPixmap(self, index, pdfpath):
paths = self.poppler.pdftocairo(pdfpath, Path('pdftocairo_temp.png'), 300)
if len(paths) > 0:
pixmap = QtGui.QPixmap( str(paths[0]) )
for path in paths:
path.unlink()
return pixmap
return None
def refreshView(self, pixmap=None, parentIndex=None):
self.recogView.clear()
if not pixmap is None:
self.recogView.addPixmap(pixmap)
if not parentIndex is None:
for item in self.fileList.childrenWithText('Rect', 0, parentIndex):
self.recogView.appendRect(item.data())
for item in self.fileList.childrenWithText('Crop', 0, parentIndex):
self.recogView.appendCrop(item.data())
for item in self.fileList.selectedChildren(0, parentIndex):
self.recogView.appendSelectedRect(item.data())
self.recogView.update()
def recognizeAll(self):
self.fileList.removeAllRect()
for row in range( self.fileList.model.rowCount() ):
parentIndex = self.fileList.index(row, 0)
pixmap = self.fileList.item(row, 1).data()
if pixmap is None:
filePath = self.fileList.filePath(row)
pixmap = self.pathToPixmap( parentIndex, filePath )
self.fileList.setFileData(row, 1, 'image', pixmap)
area_range = ( self.toolBar.imageMenu.contourAreaMin.value(), self.toolBar.imageMenu.contourAreaMax.value() )
dilate_size = ( self.toolBar.imageMenu.dilate.value(), self.toolBar.imageMenu.dilate.value() )
image_process = ImageProcessing(pixmap)
edge, rects, crops = image_process.recognize_table(area_range, dilate_size)
self.fileList.setFileData(row, 2, 'edge image', edge, QtCore.Qt.Unchecked)
for text, datas in zip(['Rect', 'Crop'], [rects, crops]):
for data in datas:
rectItem = QtGui.QStandardItem(text)
rectItem.setData(QtCore.QRectF(data[0], data[1], data[2], data[3]))
rectItem.setCheckable(True)
rectItem.setCheckState(QtCore.Qt.Checked)
rectItem.setEditable(False)
cropPixmap = pixmap.copy(QtCore.QRect(data[0], data[1], data[2], data[3]))
pixmapItem = QtGui.QStandardItem()
pixmapItem.setData(cropPixmap)
pixmapItem.setData(cropPixmap, QtCore.Qt.DecorationRole)
pixmapItem.setEditable(False)
self.fileList.appendChild( parentIndex, [rectItem, pixmapItem, QtGui.QStandardItem()] )
self.refreshView(None, None)
def recogViewClicked(self, rect):
if self.recogView.drawRectFlag:
self.fileList.addRect(rect)
self.recogView.appendRect(rect)
def removeSelectedRects(self):
removedRects, parentIndex = self.fileList.removeSelectedRects()
for rect in removedRects:
self.recogView.removeRect(rect)
pixmap = self.fileList.getImage( parentIndex.row() )
self.refreshView(pixmap, parentIndex)
def rowHeightChanged(self, value):
self.fileList.view.setStyleSheet('QTreeView::item { padding: ' + str(value) + 'px }')
def saveFiles(self):
csvDatas = []
for row in range(self.fileList.model.rowCount()):
fileItem = self.fileList.model.item(row)
for rectRow in range(fileItem.rowCount()):
csvDatas.append([
fileItem.child(rectRow, 0).text().strip(),
fileItem.child(rectRow, 2).text().strip()
])
with open('output.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(csvDatas)
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
filelist.py
ここをクリックして展開
# -*- coding: utf-8 -*-
from pathlib import Path
from PyQt5 import QtWidgets, QtCore, QtGui
class FileList(QtWidgets.QWidget):
tableClicked = QtCore.pyqtSignal(QtCore.QModelIndex)
def __init__(self, parent=None, path=None):
super(FileList, self).__init__(parent)
self.setLayout( QtWidgets.QVBoxLayout(self) )
self.model = QtGui.QStandardItemModel(self)
self.model.setHorizontalHeaderLabels(['', '', ''])
self.view = QtWidgets.QTreeView(self)
self.view.setModel(self.model)
self.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.view.setItemDelegate(Delegate())
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(self.view)
self.view.clicked.connect( lambda index : self.tableClicked.emit(index) )
def addRect(self, rect):
rows = list(set([ index.row() for index in self.view.selectedIndexes() ]))
for row in rows:
parentIndex = self.model.index(row, 0)
parentItem = self.model.item( parentIndex.row(), 1 )
pixmap = parentItem.data()
rectItem = QtGui.QStandardItem('Rect')
rectItem.setData(rect)
rectItem.setCheckable(True)
rectItem.setCheckState(QtCore.Qt.Checked)
rectItem.setEditable(False)
cropPixmap = pixmap.copy(QtCore.QRect( rect.x(), rect.y(), rect.width(), rect.height() ))
pixmapItem = QtGui.QStandardItem()
pixmapItem.setData(cropPixmap)
pixmapItem.setData(cropPixmap, QtCore.Qt.DecorationRole)
pixmapItem.setEditable(False)
self.appendChild( parentIndex, [rectItem, pixmapItem, QtGui.QStandardItem()] )
def appendChild(self, parentIndex, items):
parentItem = self.model.item( parentIndex.row() )
parentItem.appendRow(items)
def appendFile(self, path):
filename = QtGui.QStandardItem(str(path.name))
filename.setData(path)
image = QtGui.QStandardItem()
edge = QtGui.QStandardItem()
self.model.appendRow([ filename, image, edge ])
def childrenWithText(self, text, column=0, parentIndex=QtCore.QModelIndex()):
children = []
selectedIndexes = self.view.selectedIndexes()
for item in self.items(column, parentIndex):
if not item.checkState() == QtCore.Qt.Checked:
continue
if not item.text() == text:
continue
if self.model.index(item.row(), column) in selectedIndexes:
continue
children.append(item)
return children
def filePath(self, row):
return self.model.item(row).data()
def getImage(self, row):
if self.item(row, 1).checkState() == QtCore.Qt.Checked:
return self.item(row, 1).data()
if self.item(row, 2).checkState() == QtCore.Qt.Checked:
return self.item(row, 2).data()
return None
def index(self, row, column, parentIndex=QtCore.QModelIndex()):
return self.model.index(row, column, parentIndex)
def item(self, row, column=0):
return self.model.item(row, column)
def items(self, column=0, parentIndex=QtCore.QModelIndex()):
if parentIndex.isValid():
item = self.model.item( parentIndex.row() )
return [ item.child(row, column) for row in range( item.rowCount() ) ]
else:
return [ self.model.item(row, column) for row in range( self.model.rowCount() ) ]
def removeSelectedRects(self):
removeList = {}
removedRects = []
for index in self.view.selectedIndexes():
parentIndex = index.parent()
if not parentIndex.isValid():
continue
if not parentIndex in removeList:
removeList[parentIndex] = {}
parentItem = self.model.item(parentIndex.row())
item = parentItem.child(index.row())
removeList[parentIndex][index.row()] = item.data()
for parentIndex in removeList:
deleteRowAndRects = sorted( removeList[parentIndex].items(), reverse=True )
deleteRowAndRects = { key : data for key, data in deleteRowAndRects }
for deleteRow in deleteRowAndRects:
rect = deleteRowAndRects[deleteRow]
self.model.removeRow(deleteRow, parentIndex)
removedRects.append(rect)
parentIndex = list(removeList.keys())[0]
return removedRects, parentIndex
def removeAllRect(self):
for row in range( self.model.rowCount() ):
parentItem = self.item(row)
for childRow in list( range(parentItem.rowCount()) )[::-1]:
parentItem.removeRow(childRow)
def selectedChildren(self, column=0, parentIndex=QtCore.QModelIndex()):
children = []
selectedIndexes = [ [i.row(), i.column(), i.parent()] for i in self.view.selectedIndexes() ]
for item in self.items(column, parentIndex):
if not item.checkState() == QtCore.Qt.Checked:
continue
if not [item.row(), 0, parentIndex] in selectedIndexes:
continue
children.append(item)
return children
def selectedIndexes(self):
return self.view.selectedIndexes()
def setData(self, index, text, data, state=QtCore.Qt.Checked):
item = QtGui.QStandardItem(text)
item.setData(data)
item.setCheckable(True)
item.setCheckState(state)
self.model.setItem( index.row(), index.column(), item )
return item
def setFileData(self, row, column, text, data, state=QtCore.Qt.Checked):
item = QtGui.QStandardItem(text)
item.setData(data)
item.setCheckable(True)
item.setCheckState(state)
self.model.setItem( row, column, item )
return item
def setViewSize(self):
self.view.setColumnWidth(0, 150)
self.view.setColumnWidth(1, 80)
self.view.setColumnWidth(2, 80)
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(Delegate, self).__init__(parent)
def paint(self, painter, option, index):
item = index.model().itemFromIndex(index)
if type( item.data() ) is QtGui.QPixmap and not item.parent() is None:
pixmapWidth, pixmapHeight = item.data().size().width(), item.data().size().height()
cellWidth, cellHeight = option.rect.width(), option.rect.height()
if cellWidth / cellHeight < pixmapWidth / pixmapHeight:
scaledPixmap = item.data().scaledToWidth(cellWidth)
else:
scaledPixmap = item.data().scaledToHeight(cellHeight)
item.setData(scaledPixmap, QtCore.Qt.DecorationRole)
super(Delegate, self).paint(painter, option, index)
if __name__ == '__main__':
import sys
def item(file):
pixmapItem = QtGui.QStandardItem()
pixmapItem.setData(QtGui.QPixmap(file))
pixmapItem.setData(QtGui.QPixmap(file), QtCore.Qt.DecorationRole)
return pixmapItem
app = QtWidgets.QApplication(sys.argv)
view = FileList()
view.model.setHorizontalHeaderLabels([ 'column' + str(i) for i in range(3) ])
#view.model.appendRow([ QtGui.QStandardItem(str(i)) for i in range(3) ])
view.model.appendRow([ item('cell00'), item('cell01'), QtGui.QStandardItem() ])
view.model.appendRow([ item('cell10'), item('cell11'), item('cell12') ])
view.model.appendRow([ item('cell20'), item('cell21'), QtGui.QStandardItem() ])
view.resize(600, 300)
view.show()
app.exec()
iamge_processing.py
ここをクリックして展開
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pathlib import Path, WindowsPath
from PyQt5 import QtGui
class ImageProcessing(object):
def __init__(self, data):
self.data = self.load_data(data)
def cv_to_pixmap(self, cv_image):
shape_size = len(cv_image.shape)
if shape_size == 2:
rgb = cv2.cvtColor(cv_image, cv2.COLOR_GRAY2RGB)
elif shape_size == 3:
rgb = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
height, width, bytesPerComponent = rgb.shape
bytesPerLine = bytesPerComponent * width
image = QtGui.QImage(rgb.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)
qpixmap = QtGui.QPixmap.fromImage(image)
return qpixmap
def edge_image(self, size):
gray = cv2.cvtColor(self.data, cv2.COLOR_BGR2GRAY)
edge = cv2.Canny(gray, 1, 100, apertureSize=3)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, size)
dilate = cv2.dilate(edge, kernel)
return dilate
def edge_to_rects(self, edge, area_range):
contours, hierarchy = cv2.findContours(edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
rects = []
for contour, hierarchy in zip(contours, hierarchy[0]):
if not area_range[0] < cv2.contourArea(contour) < area_range[1]:
continue
curve = cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True), True)
if len(curve) == 4:
p1, p3 = curve[0][0], curve[2][0]
x, y, w, h = p1[0], p1[1], p3[0] - p1[0], p3[1] - p1[1]
rect = [x, y, w, h]
if False in [ False for r in rect if r < 1 ]:
continue
if self.same_rect_is_in_rects(rect, rects, 10):
continue
rects.append(rect)
rects = sorted( rects, key=lambda x: (x[1], x[0]) )
return rects
def load_data(self, data):
data_type = type(data)
if data_type is str or data_type is WindowsPath:
return cv2.imread( str(data) )
if data_type is QtGui.QPixmap:
return self.qimage_to_cv( data.toImage() )
def qimage_to_cv(self, qimage):
w, h, d = qimage.size().width(), qimage.size().height(), qimage.depth()
bytes_ = qimage.bits().asstring(w * h * d // 8)
arr = np.frombuffer(bytes_, dtype=np.uint8).reshape((h, w, d // 8))
return arr
def recognize_table(self, area_range=(10, 1000), dilate_size=(6, 6)):
edge = self.edge_image(dilate_size)
rects = self.edge_to_rects(edge, area_range)
crops = self.rects_to_crops(rects)
edge = self.cv_to_pixmap(edge)
return edge, rects, crops
def rects_to_crops(self, rects, margin=10):
crops = []
for rect in rects:
x, y, w, h = rect[0], rect[1], rect[2], rect[3]
cropped = self.data[ y : y + h, x : x + w ]
gray = cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY)
threshold = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)[1]
contours = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
rects_in_cropped = [ cv2.boundingRect(contour) for contour in contours[1:] ]
if len(rects_in_cropped) == 0:
continue
x1 = min([ r[0] for r in rects_in_cropped ]) - margin
y1 = min([ r[1] for r in rects_in_cropped ]) - margin
x2 = max([ r[0] + r[2] for r in rects_in_cropped ]) + margin
y2 = max([ r[1] + r[3] for r in rects_in_cropped ]) + margin
if x1 < 0:
x1 = 0
if y1 < 0:
y1 = 0
if x2 > cropped.shape[1]:
x2 = cropped.shape[1]
if y2 > cropped.shape[0]:
y2 = cropped.shape[0]
crops.append([x + x1, y + y1, x2 - x1, y2 - y1])
return crops
def same_rect_is_in_rects(self, rect1, rects, tolerance=5):
for rect2 in rects:
frag = True
for r1, r2 in zip(rect1, rect2):
if not r2 - tolerance < r1 < r2 + tolerance:
frag = False
break
if frag:
return True
return False
poppler.py
ここをクリックして展開
# -*- coding: utf-8 -*-
import subprocess
from pathlib import Path
import chardet
class Poppler():
def __init__(self, popplerPath):
pdftocairo = list(Path(popplerPath).glob('**/pdftocairo.exe'))
if len(pdftocairo) > 0:
self.pdftocairo_path = pdftocairo[0]
else:
self.pdftocairo_path = None
def pdftocairo(self, input_path, output_path, resolution):
suffix = '-'+str(output_path.suffix).replace('.', '')
cmd = [str(self.pdftocairo_path), suffix, '-r', str(resolution), str(input_path), str(output_path.stem)]
return_value = self.subprocess_run(cmd)
paths = []
count = 1
p = Path(output_path.stem + '-' + str(count) + '.png')
while p.exists():
paths.append(p)
count = count + 1
p = Path(output_path.stem + '-' + str(count) + '.png')
return paths
def subprocess_run(self, cmd):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
returncode = subprocess.Popen(cmd, startupinfo=startupinfo)
returncode.wait()
return returncode
table_recognition_view.py
ここをクリックして展開
# -*- coding: utf-8 -*-
from PyQt5 import QtWidgets, QtCore, QtGui
class TableRecognitionView(QtWidgets.QWidget):
mouseLeftReleasedSignal = QtCore.pyqtSignal(QtCore.QRectF)
def __init__(self, parent=None):
super().__init__(parent)
self.drawRectFlag = False
self.rectPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor(255, 0, 0, 100) ), 6 )
self.cropPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor( 0, 255, 0, 100) ), 6 )
self.freeHandPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor(100, 100, 0, 150) ), 6 )
self.selectedPen = QtGui.QPen( QtGui.QBrush( QtGui.QColor( 0, 0, 255, 150) ), 20 )
self.scene = QtWidgets.QGraphicsScene()
self.graphicsView = GraphicsView(self)
self.graphicsView.setScene(self.scene)
self.graphicsView.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.setLayout( QtWidgets.QVBoxLayout(self) )
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(self.graphicsView)
self.graphicsView.mouseLeftReleased.connect(self.mouseLeftReleased)
def appendCrop(self, rect):
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.cropPen)
self.scene.addItem(rectItem)
def appendRect(self, rect):
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.rectPen)
self.scene.addItem(rectItem)
def appendSelectedRect(self, rect):
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.selectedPen)
self.scene.addItem(rectItem)
def mouseLeftReleased(self, rect):
if self.drawRectFlag:
rectItem = QtWidgets.QGraphicsRectItem(rect)
rectItem.setPen(self.freeHandPen)
self.scene.addItem(rectItem)
self.mouseLeftReleasedSignal.emit(rect)
def resetPixmap(self, pixmap):
self.scene.clear()
if not pixmap is None:
self.scene.addPixmap(pixmap)
def removeRect(self, rect):
for rectItem in self.scene.items():
if type(rectItem) is QtWidgets.QGraphicsRectItem:
if rect == rectItem.rect():
self.scene.removeItem(rectItem)
def removeAllRect(self):
for rectItem in self.scene.items():
if type(rectItem) is QtWidgets.QGraphicsRectItem:
self.scene.removeItem(rectItem)
def setDrawRectFlag(self, flag):
self.drawRectFlag = flag
def setRectVisible(self, rect, isVisible):
for rectItem in self.scene.items():
if type(rectItem) is QtWidgets.QGraphicsRectItem:
if rect is rectItem.rect():
rectItem.setVisible(isVisible)
def addPixmap(self, pixmap):
self.scene.addPixmap(pixmap)
def clear(self):
self.scene.clear()
class GraphicsView(QtWidgets.QGraphicsView):
mouseLeftReleased = QtCore.pyqtSignal(QtCore.QRectF)
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.numScheduledScalings = 0
self.rect = QtCore.QRectF(0.0, 0.0, 0.0, 0.0)
def animation_finished(self):
if self.numScheduledScalings > 0:
self.numScheduledScalings -= 1
else:
self.numScheduledScalings += 1
def wheelEvent(self, event):
numDegrees = event.angleDelta().y() / 8
numSteps = numDegrees / 15
self.numScheduledScalings += numSteps
if self.numScheduledScalings * numSteps < 0:
self.numScheduledScalings = numSteps
anim = QtCore.QTimeLine(350, self)
anim.setUpdateInterval(20)
anim.valueChanged.connect(self.scaling_time)
anim.finished.connect(self.animation_finished)
anim.start()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MidButton:
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
event = QtGui.QMouseEvent(
QtCore.QEvent.GraphicsSceneDragMove,
event.pos(),
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.KeyboardModifier.NoModifier
)
elif event.button() == QtCore.Qt.LeftButton:
self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
point = self.mapToScene( event.pos() )
self.rect = QtCore.QRectF(point, point)
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
if event.button() == QtCore.Qt.LeftButton:
p2 = self.mapToScene( event.pos() )
self.rect.setBottomRight(p2)
self.mouseLeftReleased.emit(self.rect)
def scaling_time(self, x):
factor = 1.0 + float(self.numScheduledScalings) / 300.0
self.scale(factor, factor)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
view = TableRecognitionView()
view.setPixmap( QtGui.QPixmap('table1.png') )
view.resize(600, 300)
view.show()
app.exec()
tesseract_ocr.py
ここをクリックして展開
# -*- coding: utf-8 -*-
import subprocess
from pathlib import Path, WindowsPath
from PyQt5 import QtGui
class TesseractOCR():
def __init__(self, tesseractPath):
self.tesseract = tesseractPath
def command(self, filename, output_file):
return [self.tesseract, str(filename), output_file.stem]
def OCR(self, data):
if type(data) is str or type(data) is WindowsPath:
return self.OCR_file(data)
if type(data) is QtGui.QPixmap:
imagefile = Path('__temp__.png')
data.save(str(imagefile))
output = self.OCR_file(imagefile)
imagefile.unlink()
return output
def OCR_file(self, filename):
output_file = Path('__temp__.txt')
cmd = [self.tesseract, str(filename), output_file.stem]
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
returncode = subprocess.Popen(cmd, startupinfo=startupinfo)
returncode.wait()
try:
with open(output_file, 'r', encoding='utf-8') as file:
output = file.readline()
output_file.unlink()
return output
except:
return ''
toolbar.py
ここをクリックして展開
# -*- coding: utf-8 -*-
from PyQt5 import QtWidgets, QtCore, QtGui
class ToolBar(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.tab = QtWidgets.QTabWidget(self)
self.setLayout( QtWidgets.QGridLayout(self) )
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().setHorizontalSpacing(0)
self.layout().setVerticalSpacing(0)
self.layout().addWidget(self.tab)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
self.homeTab, homeTabLayout = self.addTab('Home')
self.fileMenu = FileMenu(self.homeTab, 'File')
self.viewMenu = ViewMenu(self.homeTab, 'View')
self.imageMenu = ImageMenu(self.homeTab, 'Image')
self.ocrMenu = OcrMenu(self.homeTab, 'OCR')
self.drawMenu = DrawMenu(self.homeTab, 'Draw')
homeTabLayout.addWidget(self.fileMenu)
homeTabLayout.addWidget(self.viewMenu)
homeTabLayout.addWidget(self.imageMenu)
homeTabLayout.addWidget(self.ocrMenu)
homeTabLayout.addWidget(self.drawMenu)
homeTabLayout.addSpacerItem( QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) )
def addTab(self, title):
tabWidget = QtWidgets.QWidget(self)
tabWidget.setLayout( QtWidgets.QHBoxLayout(tabWidget) )
tabWidget.layout().setContentsMargins(2, 2, 2, 2)
self.tab.addTab(tabWidget, title)
return tabWidget, tabWidget.layout()
class MenuGroup(QtWidgets.QGroupBox):
def __init__(self, parent, title):
super().__init__(parent)
self.toggleViewButton = {}
self.setLayout( QtWidgets.QVBoxLayout(self) )
self.layout().setContentsMargins(2, 2, 2, 2)
self.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding)
self.gridWidget = QtWidgets.QWidget(self)
self.layout().addWidget(self.gridWidget)
self.layout().addSpacerItem( QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding) )
label = QtWidgets.QLabel(title, self)
label.setAlignment(QtCore.Qt.AlignHCenter)
self.layout().addWidget(label)
self.gridWidgetLayout = QtWidgets.QGridLayout(self.gridWidget)
self.gridWidgetLayout.setContentsMargins(0, 0, 0, 0)
self.gridWidgetLayout.setHorizontalSpacing(0)
self.gridWidgetLayout.setVerticalSpacing(0)
def addWidget(self, widget, fromRow, fromColumn, rowSpan, columnSpan):
if widget is None:
return widget
widget.setParent(self.gridWidget)
self.gridWidgetLayout.addWidget(widget, fromRow, fromColumn, rowSpan, columnSpan)
return widget
def createButton(self, parent=None, text='', offIconPath=None, onIconPath=None, checkable=False, iconSize=24):
button = QtWidgets.QToolButton(parent)
button.setText(text)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(offIconPath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap(onIconPath), QtGui.QIcon.Normal, QtGui.QIcon.On)
button.setIcon(icon)
button.setIconSize( QtCore.QSize(iconSize, iconSize) )
button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
button.setAutoRaise(True)
button.setArrowType(QtCore.Qt.NoArrow)
button.setCheckable(checkable)
button.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
return button
def createSlider(self, parent, orientation, minimum, maximum, maximumWidth=100):
slider = QtWidgets.QSlider(orientation)
slider.setMinimum(minimum)
slider.setMaximum(maximum)
slider.setMaximumWidth(maximumWidth)
return slider
def addToggleViewButton(self, action, fromRow, fromColumn, rowSpan, columnSpan):
toolButton = QtWidgets.QToolButton(self)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap('./icons/task-line.svg'), QtGui.QIcon.Normal, QtGui.QIcon.Off)
icon.addPixmap(QtGui.QPixmap('./icons/task-fill.svg'), QtGui.QIcon.Normal, QtGui.QIcon.On)
toolButton.setArrowType(QtCore.Qt.NoArrow)
toolButton.setAutoRaise(True)
toolButton.setCheckable(True)
toolButton.setIcon(icon)
toolButton.setIconSize( QtCore.QSize(24, 24) )
toolButton.setText( action.text() )
toolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toolButton.click()
toolButton.toggled.connect( lambda : action.trigger() )
toolButton.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.toggleViewButton[toolButton.text()] = toolButton
self.addWidget(toolButton, fromRow, fromColumn, rowSpan, columnSpan)
class FileMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.openButton = self.createButton(self, 'Open', './icons/file-line.svg')
self.saveButton = self.createButton(self, 'Save', './icons/save-3-line.svg')
self.openButton = self.addWidget(self.openButton, 0, 0, 1, 1)
self.saveButton = self.addWidget(self.saveButton, 1, 0, 1, 1)
class ViewMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.rowHeight = SpinBox(self, 'Row height ', 1, 100, 5, 40)
self.addWidget(self.rowHeight, 1, 0, 1, 1)
class ImageMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.recognizeButton = self.createButton(self, 'Recognize all', './icons/image-line.svg')
self.contourAreaMin = SpinBox(self, 'Contour area min ', 1, 999999999, 5000)
self.contourAreaMax = SpinBox(self, 'Contour area max ', 1, 999999999, 1000000)
self.dilate = SpinBox(self, 'Dilate ', 1, 999999999, 3)
self.addWidget(self.recognizeButton, 0, 0, 1, 1)
self.addWidget(self.contourAreaMin, 0, 1, 1, 1)
self.addWidget(self.contourAreaMax, 1, 1, 1, 1)
self.addWidget(self.dilate, 2, 1, 1, 1)
class OcrMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.ocrButton = self.createButton(self, 'OCR', './icons/search-line.svg')
self.addWidget(self.ocrButton, 0, 0, 1, 1)
class DrawMenu(MenuGroup):
def __init__(self, parent, title):
super().__init__(parent, title)
self.drawRectButton = self.createButton(self, 'Draw rect', './icons/edit-box-line.svg', './icons/edit-box-line.svg', True)
self.deleteRectButton = self.createButton(self, 'Delete rect', './icons/delete-bin-line.svg')
self.addWidget(self.drawRectButton, 0, 0, 1, 1)
self.addWidget(self.deleteRectButton, 1, 0, 1, 1)
class SpinBox(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent, text, minimum, maximum, value, width=80):
super().__init__(parent)
self.setLayout( QtWidgets.QHBoxLayout(self) )
self.layout().addWidget( QtWidgets.QLabel(text, self) )
self.spinBox = QtWidgets.QSpinBox(self)
self.spinBox.setMinimum(minimum)
self.spinBox.setMaximum(maximum)
self.spinBox.setMinimumWidth(width)
self.spinBox.setMaximumWidth(width)
self.spinBox.setValue(value)
self.layout().addWidget(self.spinBox)
self.layout().setContentsMargins(0, 0, 0, 0)
self.spinBox.valueChanged.connect(lambda value : self.valueChanged.emit(value))
def value(self):
return self.spinBox.value()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
view = ToolBar()
view.show()
app.exec()