Skip to content

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:

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:

A single example of an original image on the left and the rotated version on the right, showing how it was successfully rotated by OpenCV in Python.

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!

Have Your Say

The following HTML is permitted:
<a href="" title=""> <b> <blockquote cite=""> <code> <em> <i> <q cite=""> <strike> <strong>

Comments will be published subject to the Editorial Policy.