Selectively rotating images in Python recursively
Use OpenCV in Python to recurse through directories and rotate selected images based on a string in their file name.
I have directories filled with thousands of different image files. Some of these images need rotating and some of them don’t. The file name of the image tells me whether I need to rotate the image or not; those that require rotation have “deg” in the file name.
What I want is a Python script that will recursively search my directories, identify image files that match the criteria that I set for my file name and rotate these images. If my script encounters other files in these directories that aren’t images, then I want my script to gracefully ignore them.
To achieve this, I will use several python modules:
Part One: A script to iterate through a folder recursively
The first step is to write a script that will iterate over a directory of files and do something. Because there are variables that will be entered into my script on the command line (ie: the directory path and the string in the file name), I’ll use argparse
to collect these.
def options():
parser = argparse.ArgumentParser(description="Return a recursive list of files that match a criterion")
parser.add_argument("-f", "--folder", help="Target folder of images.", required=True)
parser.add_argument("-p", "--pattern", help="String to search for", required=True)
args = parser.parse_args()
return args
The second problem that I need to overcome is the Windows file path structure, which differs from Unix-type systems. I’ll include a script that will amend Windows file paths into Python-friendly paths (ie no back-slashes) and work in all environments:
# Identify the target directory
target_raw = args.folder
# Clean up Windows file paths
if sys.platform.startswith('win'):
target_raw = target_raw.replace('\\', '/')
if not target_raw.endswith('/'):
target = target_raw+"/"
else:
target = os.path.join(target_raw, '') # Add trailing slash if missing
Since I only want my script to work on certain image files (JPEG, PNG), I’ll specify the file extensions in a variable called ext
. This will prevent my code from breaking if it finds another non-image file that also has “deg” in its name.
# Set acceptable extensions
ext = (('.png', '.PNG', '.jpg', '.JPG'))
The next step is to iterate over the directory recursively using os
. To test this, I can print the file paths that meet our criteria:
for root, dirnames, filenames in os.walk(target):
if not root.endswith('/'):
root = root+"/"
for filename in fnmatch.filter(filenames, args.pattern):
if filename.endswith(ext):
filepath = os.path.join(root, filename)
print(filepath)
You’ll notice that I had to add some additional code to deal with Windows-type subdirectory paths that would otherwise contain a mix of backward and forward slashes.
Now I can put all this together into a script that will recursively search my directory for files that meet my criteria and return the file names (via print
):
#!/usr/bin/env python
import os
import fnmatch
import argparse
import sys
def options():
parser = argparse.ArgumentParser(description="Return a recursive list of files that match a criterion")
parser.add_argument("-f", "--folder", help="Target folder of images.", required=True)
parser.add_argument("-p", "--pattern", help="String to search for", required=True)
args = parser.parse_args()
return args
def list_files():
# Get options
args = options()
# Identify the target directory
target_raw = args.folder
# Set acceptable extensions
ext = (('.png', '.PNG', '.jpg', '.JPG'))
# Clean up Windows file paths
if sys.platform.startswith('win'):
target_raw = target_raw.replace('\\', '/')
if not target_raw.endswith('/'):
target = target_raw+"/"
else:
target = os.path.join(target_raw, '') # Add trailing slash if missing
for root, dirnames, filenames in os.walk(target):
if not root.endswith('/'):
root = root+"/"
for filename in fnmatch.filter(filenames, args.pattern):
if filename.endswith(ext):
filepath = os.path.join(root, filename)
print(filepath)
if __name__ == '__main__':
list_files()
If I save my file as recurse_files.py
, I can run this on the command line as follows (where I want to find images with “deg” in their name):
recurse_files.py -f /path/to/files -p *deg*
Note the use of the -f
(file path) and -p
(pattern) flags. The output should be a list of files that meet the criteria.
Part Two: Image Rotation
I will be using OpenCV to rotate my images, although there are other modules available to do this.
This is a case of substituting the print code for image rotation code:
image = cv2.imread(filepath)
rotated = cv2.rotate(image, 1)
cv2.imwrite(filepath, rotated)
file_list.append(filepath)
Within cv2.rotate
, I am using a flag to indicate the type of rotation although I could also use the full cv
statement instead:
- 0 = cv::ROTATE_90_CLOCKWISE (rotates the image 270°)
- 1 = cv::ROTATE_180 (rotates the image 180°)
- 2 = cv::ROTATE_90_ COUNTERCLOCKWISE (rotates the image 90°)
In my code example above, I am rotating my images 180°.
Now I can put this all together into my final Python script, which I will call rotate_images.py
:
#!/usr/bin/env python
import os
import fnmatch
import argparse
import sys
import cv2
def options():
parser = argparse.ArgumentParser(description="Return a recursive list of files that match a criterion")
parser.add_argument("-f", "--folder", help="Target folder of images.", required=True)
parser.add_argument("-p", "--pattern", help="String to search for", required=True)
args = parser.parse_args()
return args
def list_files():
# Get options
args = options()
# Identify the target directory
target_raw = args.folder
# Set acceptable extensions
ext = (('.png', '.PNG', '.jpg', '.JPG'))
# Clean up Windows file paths
if sys.platform.startswith('win'):
target_raw = target_raw.replace('\\', '/')
if not target_raw.endswith('/'):
target = target_raw+"/"
else:
target = os.path.join(target_raw, '') # Add trailing slash if missing
for root, dirnames, filenames in os.walk(target):
if not root.endswith('/'):
root = root+"/"
for filename in fnmatch.filter(filenames, args.pattern):
if filename.endswith(ext):
filepath = os.path.join(root, filename)
# Rotate images
image = cv2.imread(filepath)
rotated = cv2.rotate(image, 1)
cv2.imwrite(filepath, rotated)
if __name__ == '__main__':
list_files()
To execute this code, I can run the following command:
rotate_images.py -f /path/to/files -p *deg*
A quick check of the result:
Additional Options
If desired, it is possible to cast the list of rotated images to a Python list if a record of the rotated images is required:
file_list = []
for root, dirnames, filenames in os.walk(target):
if not root.endswith('/'):
root = root+"/"
for filename in fnmatch.filter(filenames, args.pattern):
if filename.endswith(ext):
filepath = os.path.join(root, filename)
# Rotate images
image = cv2.imread(filepath)
rotated = cv2.rotate(image, 1)
cv2.imwrite(filepath, rotated)
file_list.append(filepath)
with open("file_rotations.txt", "w") as output:
output.write("\n".join(map(str, file_list)))
In this case, I made a list called file_list
, which was saved to file_rotations.txt
.
Another option is to enter the rotation angle as a variable in the command line. This can be achieved with argparse
and a -d
flag as follows:
parser.add_argument("-d", "--degrees", help="Rotation angle code", required=True)
then:
# Translate rotation angle into OpenCV command
if args.degrees == "270":
angle = 0
elif args.degrees == "180":
angle = 1
elif args.degrees == "90":
angle = 2
else:
print("Please enter a valid angle [90, 180 or 270] to proceed.")
exit()
Finally, add the angle
variable to the Python image rotation command:
rotated = cv2.rotate(image, angle)
You could then rotate all of the target images 270-degrees via the following code (for example):
rotate_images.py -f /path/to/files -p *deg* -d 270
This would give the script maximum flexibility. The full code utilising this option is available on GitHub Gist.
Comments
No comments have yet been submitted. Be the first!