Aligning scanned images of animated video paper using OpenCV and Python

Using OpenCV and Python, I made a tool that detects the alignment holes of animation video paper as shown below and removes the misalignment of the scanned image. A practical article for beginners in OpenCV and Python. See here for OpenCV peascan.gif

The problem you want to solve

When creating an animation, it is easy to use 2D keyframe application like 9VAe Kyubee, but if you draw one by one on video paper, it will be several hundred. It will be a lot more than one. If this is input to a personal computer with a scanner with an auto feeder, the position of several dots will inevitably shift, and the image will be blurred when played back.

Therefore, let's create a tool "Peascan.py" that detects holes on the video paper and corrects the misalignment by image processing.

input Serial number with misalignment 100 JPEG images
output Serial number with no misalignment 100 JPEG images
environment Windows

Hole recognition, image rotation, and misalignment correction should be easy with OpenCV.

1. 1. Install OpenCV and Python

--I decided to use Python (x, y), which is easy to install, so that anyone can use it. You can install OpenCV and Python at the same time.

procedure Contents Supplement
1.download http://python-xy.github.io/downloads.htmlFrompython(x,y)-2.7.10.0.exe Clickherefordetailedinstallationmethod
2.Installation Click the "Next" button
On the "Choose Components" screen, click "↓" to the right of "Custom" and click "FullClick. After that, "Next" and "Install"
You can also open Custom and check OpenCV.

It installs Python 2.7.10, OpenCV 2.4.12, which is a little earlier, but it works fine.

2. Load and display images

2-1. Creating a Python program

Let's create a simple Python + OpenCV program.

item point
Character code 「UTF8」。メモ帳で保存する場合、「ファイル」>「名前を付けて保存」>下の「Character code」を「UTF-Save as "8".
extension .py
Run From the command promptpython xxx.py

Save the following text in "UTF-8" with the name "test.py". If Japanese is included in the path, it may not work, so it is a good idea to create a folder such as c: \ pytest and save it. xx Sample image In place of xx, prepare an appropriate image file and enter the path name (c: /pytest/test.jpg, etc.). Japanese names cannot be used. The path delimiter is "/".

import numpy as np
import cv2
img = cv2.imread('C:/xx sample image xx.jpg')
print img.shape
print img.shape[0], img.shape[1]
cv2.imshow('Title',img)
cv2.waitKey(5000)

Each has the following meanings.

item Example of use Description
Numerical calculation import numpy as np Numerical calculationライブラリを使う
Image processing import cv2 OpenCV Image processingライブラリを使う
Load image img = cv2.imread('C:/sample image.jpg') Japanese cannot be used for files and path names, and the path delimiter is "/」
Variable display print 「,Display anything by separating them with ""
Image size img.shape Image img(Height, width, number of channels)
Image display cv2.imshow('Title',img) Display image img in window
pause cv2.waitKey(5000) Stop for 5 seconds, wait for key input if 0

2-2. Program execution

  1. Open the folder containing "test.py"
  2. Click the right button where there is nothing> click "Open IPython console here"
    This will open the terminal for Python.
  3. Type python test.py and press Enter
  4. If the image file is specified correctly, the image will be displayed for 5 seconds. If "None" or "Attribute Error" is displayed, (1) Japanese is not included in the location or file name of the image, and (2) the path delimiter is "" ("/". (Must be), (3) Make sure that the file names are correct.

3. 3. Find the center of gravity by binarizing the mark (hole) in the upper left of the image

After displaying the image, rewrite it as follows from the line following "img =" in "test.py". Assuming that there is a mark in the upper left (0,0)-(200,200) range of the image, find the center of gravity. Adjust the values of frmX, toX, frmY, toY according to the size of the image.

import numpy as np
import cv2
img = cv2.imread('C:/xx sample image xx.jpg')

frmX,toX = 0,200 #Range of marks (holes)
frmY,toY = 0,200 #Range of marks (holes)
mark = img[frmY:toY, frmX:toX] #Partial image
gray = cv2.cvtColor(mark, cv2.COLOR_BGR2GRAY) #Monochrome
ret, bin = cv2.threshold(gray, 127, 255, 0) #Binarization
cv2.imshow('out',bin) #Mark range
cv2.waitKey(1000) #Stop for 1 second
contours, hierarchy = cv2.findContours(bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) #Extraction of contour
cnt = contours[0] #First contour
M = cv2.moments(cnt) #Moment
cx = int(M['m10']/M['m00']) #Center of gravity X
cy = int(M['m01']/M['m00']) #Center of gravity Y
cv2.circle(img,(cx,cy), 10, (0,0,255), -1)
print cx,cy
cv2.imshow('Title',img)
cv2.waitKey(5000) #5 seconds display

item Example of use Description
Multiple simultaneous assignments frmX,toX = 200,600 frmX=200 toX=Same as 600
You can assign multiple values with one function
Partial image img[frmY:toY, frmX:toX] img(frmX,frmY)-(toX,toY)Take out
Monochrome gray = cv2.cvtColor(mark, cv2.COLOR_BGR2GRAY) Create monochrome gray from color image mark
Binarization ret, bin = cv2.threshold(gray, 127, 255, 0) grayをBinarizationして bin を作成
Black and white inversion bin = ~bin Not operation on the entire array bin
Extraction of contour contours, hierarchy = cv2.findContours(bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) contours have contours
Center of gravity of contour M = cv2.moments(cnt)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
M['m00']Is the area of the contour
Draw a circle cv2.circle(img,(cx,cy), 10, (0,0,255), -1) 半径10ドットの赤いDraw a circle

4. Find the center of gravity of the marks (holes) in the upper left and upper right, and transform them to match the center of gravity of the reference image.

Let's rewrite "test.py" as follows. Detects the center of gravity of the mark (hole) in the range of 200x200 in the upper left and upper right of the image, and transforms it to match the position of the reference image.

import numpy as np
import cv2
img = cv2.imread('C:/xx sample image xx.jpg')

frmX,toX = 0,200 #Range of marks (holes)
frmY,toY = 0,200 #Range of marks (holes)

def searchMark(img, left): #Function to find a mark (hole) left==1 is left
	if left==1: #Find the mark (hole) on the left
		mark = img[frmY:toY, frmX:toX]
	else: #Find the mark (hole) on the right
		mark = img[frmY:toY, img.shape[1]-toX:img.shape[1]-frmX]
	gray = cv2.cvtColor(mark, cv2.COLOR_BGR2GRAY) #Monochrome
	ret, bin = cv2.threshold(gray, 127, 255, 0) #Binarization
	cv2.imshow('out',bin) #Display the range of marks (holes)
	cv2.waitKey(1000) #Stop for 1 second
	contours, hierarchy = cv2.findContours(bin, cv2.RETR_TREE, 	cv2.CHAIN_APPROX_SIMPLE) #Extraction of contour
	ax = ay = sum = 0. #Accumulation of the entire center of gravity
	for cnt in contours: #Find the center of gravity of the whole
		M = cv2.moments(cnt)
		ax += M['m10']
		ay += M['m01']
		sum += cv2.contourArea(cnt)
	if left==1:
		cx = ax/sum+frmX
		cy = ay/sum+frmY
	else:
		cx = ax/sum + img.shape[1]-toX
		cy = ay/sum + frmY
	cv2.circle(img,(int(cx),int(cy)), 10, (0,0,255), -1) #Draw a red circle at the center of gravity
	print cx,cy #Display the calculated center of gravity
	return cx,cy #Function return value

#Affine transformation test
cx0,cy0 = searchMark(img,1) #Center of gravity (reference) of the mark (hole) on the upper left
dx0,dy0 = searchMark(img,0) #Center of gravity (reference) of the mark (hole) on the upper right
cx1,cy1 = cx0,cy0
dx1,dy1 = dx0,dy0+10 #Suppose that the mark (hole) on the upper right is shifted downward by 10 dots.
cv2.circle(img,(int(dx1),int(dy1)), 10, (255,0,0), -1) #Draw a blue circle at the offset point
pts2 = np.float32([[cx0,cy0],[dx0,dy0],[cx0-(dy0-cy0),cy0+(dx0-cx0)]])
pts1 = np.float32([[cx1,cy1],[dx1,dy1],[cx1-(dy1-cy1),cy1+(dx1-cx1)]])
height,width,ch = img.shape
M = cv2.getAffineTransform(pts1,pts2)
dst = cv2.warpAffine(img,M,(width,height))
cv2.imshow('Title',img) #Display the image before conversion
cv2.waitKey(5000) #5 seconds display
cv2.imshow('Title',dst) #Display the converted image
cv2.waitKey(5000) #5 seconds display

item Example of use Description
Function definition def searchMark(img, left): The inside of the function is lowered one step. return cx,Can return multiple values like cy
If statement if left==1: else: If statementの中は、一段下げる。
For loop for cnt in contours: Execute all the contents of contours. The inside of For is lowered one step.
Contour area cv2.contourArea(cnt) M['m00']Same value as
Affine transformation coefficient M = cv2.getAffineTransform(pts1,pts2) M is a conversion coefficient that makes 3 points pts1 correspond to pts2.
Affine transformation dst = cv2.warpAffine(img,M,(width,height)) Convert image img to create image dst

When executed, the image will be converted so that the intentionally shifted blue circle overlaps the original red circle. This completes the mark (hole) detection and conversion process.

5. Read the images in the folder and output the aligned images to another folder

Now that the basic processing is complete, let's use it as follows. With this, even if there are hundreds of images in the folder, you can easily convert them. This tool is named "peascan.py". (Abbreviation for Position Error correction After SCAN)

How to use "Peascan" the folder containing the image.Dragging on the "py" icon creates a folder called "out" in the same location as the image, and puts the correction result with the same image name.

Alignment mark (hole) range frmX, toX =, frmY, toY = Adjust the numbers after that according to the actual image.

peascan.py


import numpy as np
import cv2
import sys #Get argv
import os  #File operations

argv = sys.argv  #Get a list of command line arguments
argc = len(argv) #Number of arguments
if argc == 2:     #Check if it is a folder
	if os.path.isdir(argv[1]) != True: #If not a folder
		argc = -1       #Make an error
if argc != 2:     #Show usage
	print 'Usage: Drag Image folder onto this icon.'
	key = raw_input('Hit Enter')
	quit()        #End

#Range to check the alignment mark (hole) ★ Adjust as appropriate ★
frmX,toX = 10,200 #10 from the horizontal edge-200 dots (symmetrical)
frmY,toY = 10,200 #10 from the top-200 dots

def searchMark(img, left): #Function to find a mark (hole) left==1 is left
	if left==1: #Find the mark (hole) on the left
		mark = img[frmY:toY, frmX:toX]
	else: #Find the mark (hole) on the right
		mark = img[frmY:toY, img.shape[1]-toX:img.shape[1]-frmX]
	gray = cv2.cvtColor(mark, cv2.COLOR_BGR2GRAY) #Monochrome
	ret, bin = cv2.threshold(gray, 127, 255, 0) #Binarization
	cv2.imshow('out',bin) #Display the range of marks (holes)
	cv2.waitKey(1000) #Stop for 1 second
	contours, hierarchy = cv2.findContours(bin, cv2.RETR_TREE, 	cv2.CHAIN_APPROX_SIMPLE) #Extraction of contour
	ax = ay = sum = 0. #Accumulation of the entire center of gravity
	for cnt in contours: #Find the center of gravity of the whole
		M = cv2.moments(cnt)
		ax += M['m10']
		ay += M['m01']
		sum += cv2.contourArea(cnt)
	if left==1:
		cx = ax/sum+frmX
		cy = ay/sum+frmY
	else:
		cx = ax/sum + img.shape[1]-toX
		cy = ay/sum + frmY
	cv2.circle(img,(int(cx),int(cy)), 10, (0,0,255), -1) #Draw a red circle at the center of gravity
	print cx,cy #Display the calculated center of gravity
	return cx,cy #Function return value

#Main loop
inpFolder = argv[1]                    #Input image folder
parent = os.path.dirname(argv[1])
outFolder = os.path.join(parent,'out') #Output image folder
if os.path.exists(outFolder) != True:  #If it does not exist
	os.mkdir(outFolder)            #Create output folder
files = [f for f in os.listdir(inpFolder)] #Input image
files.sort(key=os.path.basename)       #Sort by file name
cx0 = -1
for fn in files:
	img = cv2.imread(os.path.join(inpFolder,fn))
	if img is None:    #I couldn't read it, so next
		continue    
	if cx0 == -1: #Remember the first image as it is
		cx0,cy0=searchMark(img,1)
		dx0,dy0=searchMark(img,0)
		cv2.imwrite(os.path.join(outFolder,fn), img)
	else: #The second and subsequent images are affine-transformed to match the first image
		cx1,cy1=searchMark(img,1)
		dx1,dy1=searchMark(img,0)
		pts2 = np.float32([[cx0,cy0],[dx0,dy0],[cx0-(dy0-cy0),cy0+(dx0-cx0)]])
		pts1 = np.float32([[cx1,cy1],[dx1,dy1],[cx1-(dy1-cy1),cy1+(dx1-cx1)]])
		height,width,ch = img.shape
		M = cv2.getAffineTransform(pts1,pts2)
		dst = cv2.warpAffine(img,M,(width,height))
		cv2.imwrite(os.path.join(outFolder,fn), dst) #Writing image
cv2.imshow('Title',dst) #Show last image
cv2.waitKey(5000) #5 seconds display

item Example of use Description
Command arguments import sys
argv = sys.argv
Put command line arguments in a character array
File operations import os
Number of arrays argc = len(argv) argc is the number of contents of the array argv
Folder judgment os.path.isdir(argv[1]) argv[1]True if is the path of the folder
Key input key = raw_input('Hit Enter') Enter a string in key
End quit() プログラムをEndする
Parent folder os.path.dirname(argv[1]) Path argv[1]Take out from the end to the second
File/Folder name os.path.basename(argv[1]) Path argv[1]Extract the last name of
Combine folders and files os.path.join(parent,'out') File name in folder path parent'out'Connect
Existence check os.path.exists(outFolder) True if outFolder exists
Create folder os.mkdir(outFolder) Create folder outFolder
File list creation files = [f for f in os.listdir(inpFolder)] All filenames in the inpFolder folder are in the array files
Sort by file name files.sort(key=os.path.basename) 配列 files をSort by file name
In case of error if img is None: Use is or is not when comparing to None
For interruption continue Suspend processing in For statement and proceed to the next
Save image cv2.imwrite(os.path.join(outFolder,fn), img) Save the image img as fn in outFolder

――The tool created this time only calculates the center of gravity of the marks in the upper left and upper right of the image, so the shape is completely irrelevant. Even if it is a cross mark like a dragonfly, it can be used for alignment if all the images have the same shape. --The program is written in Python, so you can easily modify it. Please use all means.

6. Note the difference between OpenCV 3 and 2.4

Most of the OpenCV information on the net is from OpenCV 3, and OpenCV 2.4 sometimes gave an error.

item OpenCV 3 OpenCV 2.4
labeling nLabels, labelImage = cv2.connectedComponents(bin) connected Components cannot be used
Contour extraction image, contours, hierarchy = cv2.findContours( thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) contours, hierarchy = cv2.findContours( bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
2 outputs
drawing img = cv2.circle(img, center, radius,(0,255,0),2) cv2.circle(img, center, radius, (0,255,0),2)
出力はなし、imgに直接drawingされる

Related article

  1. Let's make an animation with free software (9VAe Kyubee) -Scratch, biscuits, then 9VAe = 3rd programming learning environment
  2. How to make stroke order animation
  3. 9VAe Kyubee: How to make a long animation -Create APNG for moving LINE stamps: Free software 9VAe Kyubee -GIF animation that can be made without drawing a picture with [9VAe Kyubee] -SVG animation created with 9VAe Kyubee -How to customize animation software 9VAe -Modify 9VAe for Kids Plaza Osaka -9VAe / 9svg data format explanation -Aligning scanned images of animated video paper using OpenCV and Python

Recommended Posts

Aligning scanned images of animated video paper using OpenCV and Python
Try projective transformation of images using OpenCV with Python
Environment construction of python and opencv
Capturing images with Pupil, python and OpenCV
Easy introduction of python3 series and OpenCV3
Video processing using Python + OpenCV on Mac
Get and estimate the shape of the head using Dlib and OpenCV with python
Head orientation estimation using Python and OpenCV + dlib
I tried object detection using Python and OpenCV
Anonymous upload of images using Imgur API (using Python)
Wavelet transform of images with PyWavelets and OpenCV
Collection and automation of erotic images using deep learning
I tried "morphology conversion" of images with Python + OpenCV
Python application: Data cleansing # 3: Use of OpenCV and preprocessing of image data
Verification and implementation of video reconstruction method using GRU and Autoencoder
Convert video to black and white with ffmpeg + python + opencv
Reconstruction of cone beam CT (CBCT) using python and TIGRE
Shoot time-lapse from a PC camera using Python and OpenCV
[Python] Accessing and cropping image pixels using OpenCV (for beginners)
Save images using python3 requests
[Python] Using OpenCV with Python (Basic)
python: Basics of using scikit-learn ①
Faster loading of Python images
Source installation and installation of Python
Using OpenCV with Python @Mac
[Image processing] Edge detection using Python and OpenCV makes Poo naked!
Build and try an OpenCV & Python environment in minutes using Docker
How to save only a part of a long video using OpenCV
Get and set the value of the dropdown menu using Python and Selenium
[Python] Implementation of Nelder–Mead method and saving of GIF images by matplotlib
Object extraction in images by pattern matching using OpenCV with Python
python> No #ifdef> Another solution> __debug__ Pros and cons of using
[Python] Try to recognize characters from images with OpenCV and pyocr
How to put OpenCV in Raspberry Pi and easily collect images of face detection results with Python
Shining life with Python and OpenCV
Pixel manipulation of images in Python
Vertical Tower of Pisa using OpenCV
The story of Python and the story of NaN
Image capture of firefox using python
Benefits and examples of using RabbitMq
[Python] Using OpenCV with Python (Image Filtering)
Installation of SciPy and matplotlib (Python)
Judgment of backlit image using OpenCV
Authentication using tweepy-User authentication and application authentication (Python)
[Python] Using OpenCV with Python (Edge Detection)
Send messages and images using LineNotify
Removal of haze using Python detailEnhanceFilter
I tried using GrabCut of OpenCV
Generating multilingual text images using Python
This and that of python properties
Clustering and visualization using Python and CytoScape
Implementation of desktop notifications using Python
Load gif images with Python + OpenCV
Coexistence of Python2 and 3 with CircleCI (1.0)
Summary of Python indexes and slices
Basic study of OpenCV with Python
Reputation of Python books and reference books
[OpenCV; Python] Summary of findcontours function
Put OpenCV in OS X with Homebrew and input / output video with python
Reading and creating a mark sheet using Python OpenCV (Tips for reading well)
Development and deployment of REST API in Python using Falcon Web Framework