Dear Internet Explorer user: Your browser is no longer supported

Please switch to a modern browser such as Microsoft Edge, Mozilla Firefox or Google Chrome to view this website's content.

# 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
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:

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
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.

<a href="" title=""> <b> <blockquote cite=""> <code> <em> <i> <q cite=""> <strike> <strong>