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
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.
--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.
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 | 「,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 |
python test.py
and press EnterAfter 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 |
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.
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.
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される |
Recommended Posts