-2

Currently, I have a python program that is able to run without error. However, it is only able to run through a subfolder of images and extract the x, y, z coordinates of the 468 facial landmarks in each image. I want to edit it as such that the program will loop through the many subfolders and read the many images inside each subfolder. What needs to be stated in the "path" function and what needs to be edited in my code stated below? The folder is named as "nopain" and the subfolders are named as "1, 2, 3, etc..."

import os
import cv2
import mediapipe as mp
import time
from os import listdir
import matplotlib.pyplot as plt
from pathlib import Path
import glob
import numpy
path = glob.glob("C:/Users/Downloads/Mac master DB_no overlap/nopain/1/*.png")
fh = open('out.txt', 'w')
for file in path:
    img = cv2.imread(file)
    mpDraw = mp.solutions.drawing_utils
    mpFaceMesh = mp.solutions.face_mesh
    facemesh = mpFaceMesh.FaceMesh(max_num_faces=1)
    drawSpec = mpDraw.DrawingSpec(thickness=1, circle_radius=2)
    rgb_image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    result = facemesh.process(rgb_image)
    if result.multi_face_landmarks:
        for faceLms in result.multi_face_landmarks:
            mpDraw.draw_landmarks(img, faceLms, mpFaceMesh.FACEMESH_CONTOURS,
            drawSpec, drawSpec)
            for lm in faceLms.landmark:
                print(lm, file, file = fh)
cv2.imshow("image", img)
cv2.destroyAllWindows()
fh.close()
Michael Szczesny
  • 4,911
  • 5
  • 15
  • 32
Mohamed
  • 1
  • 5
  • This is going to be incredibly CPU intensive. Have you considered multiprocessing? If not, you should. Also, why are you calling *cv2.imshow()* on the last image processed? Seems a bit odd – DarkKnight Oct 06 '22 at 07:41
  • @OldBill sorry I have not tried multiprocessing, how can that be done? also what should I change the "cv2.imshow()" to then? – Mohamed Oct 06 '22 at 08:33
  • 4
    Please don't vandalize your post by deleting its content. – Michael Szczesny Nov 26 '22 at 12:08

3 Answers3

1

You need a recursive glob as follows. Note the double asterisk

path = glob.glob("C:/Users/Downloads/Mac master DB_no overlap/nopain/**/*.png", recursive=True)
DarkKnight
  • 19,739
  • 3
  • 6
  • 22
0

Checkout the pathlib module https://docs.python.org/3/library/pathlib.html

This should work:

import pathlib
path = pathlib.Path("C:/Users/Downloads/Mac master DB_no overlap/nopain").rglob("*.png")

For example I am using bash to generate some dummy directories and files to show how it works..

$ mkdir -p {1,2,3,4}/dir{1,2,3}
$ touch {1,2,3,4}/dir{1,2,3}/img.png
$ touch {1,2,3,4}/img1.png
$ tree
.
├── 1
│   ├── dir1
│   │   └── img.png
│   ├── dir2
│   │   └── img.png
│   ├── dir3
│   │   └── img.png
│   └── img1.png
├── 2
│   ├── dir1
│   │   └── img.png
│   ├── dir2
│   │   └── img.png
│   ├── dir3
│   │   └── img.png
│   └── img1.png
├── 3
│   ├── dir1
│   │   └── img.png
│   ├── dir2
│   │   └── img.png
│   ├── dir3
│   │   └── img.png
│   └── img1.png
└── 4
    ├── dir1
    │   └── img.png
    ├── dir2
    │   └── img.png
    ├── dir3
    │   └── img.png
    └── img1.png

Here is the output using the pathlib module:

>>> import pathlib
>>> for p in pathlib.Path("./").rglob("*.png"):
...     print(p)
...
2/img1.png
2/dir3/img.png
2/dir1/img.png
2/dir2/img.png
1/img1.png
1/dir3/img.png
1/dir1/img.png
1/dir2/img.png
3/img1.png
3/dir3/img.png
3/dir1/img.png
3/dir2/img.png
4/img1.png
4/dir3/img.png
4/dir1/img.png
4/dir2/img.png

TheAnalogyGuy
  • 376
  • 2
  • 9
0

Instead of using a recursive glob as others have suggested, I would recommend using the following code so that you have access to the name of the subfolder currently being processed - which will likely be useful for you to name the output files with the landmarks.

base_path = Path("C:/Users/Downloads/Mac master DB_no overlap/nopain")
subfolders = [item for item in base_path.iterdir() if item.is_dir()]
for subfolder in subfolders:
    with open(f'out_{str(subfolder.name)}.txt', 'w') as fh:
        print(f"Working on subfolder '{str(subfolder)}'...")
        for file in subfolder.glob("*.png"):
            img = cv2.imread(str(file))
            mpDraw = mp.solutions.drawing_utils
            mpFaceMesh = mp.solutions.face_mesh
            facemesh = mpFaceMesh.FaceMesh(max_num_faces=1)
            drawSpec = mpDraw.DrawingSpec(thickness=1, circle_radius=2)
            rgb_image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            result = facemesh.process(rgb_image)
            if result.multi_face_landmarks:
                for faceLms in result.multi_face_landmarks:
                    mpDraw.draw_landmarks(img, faceLms, mpFaceMesh.FACEMESH_CONTOURS,
                    drawSpec, drawSpec)
                    for lm in faceLms.landmark:
                        print(lm, file, file=fh)
        cv2.imshow("image", img)
        cv2.destroyAllWindows()

With this code, the output files with landmarks will be named appropriately: e.g., out_1.txt for subfolder 1, out_2.txt for subfolder 2, etc.

You can also save out.txt to the subfolder itself. Since subfolder is a pathlib.Path object, use the line with open(str(subfolder / 'out.txt'), 'w') as fh:.

Raj K
  • 329
  • 1
  • 7
  • 1
    fh = open(f'out_{str(subfolder)}.txt', 'w') OSError: [Errno 22] Invalid argument: 'out_C:\\Users\\\\Downloads\\Mac master DB_no overlap\\nopain\\1.txt' – Mohamed Oct 06 '22 at 07:29
  • Received that error @Raj K – Mohamed Oct 06 '22 at 07:30
  • My apologies, it should be `fh = open(f'out_{str(subfolder.name)}.txt', 'w')`. `str(subfolder)` returns the full path, but we just want the name (e.g., `1`) in this case. I'll update my answer to fix this. – Raj K Oct 06 '22 at 16:58
  • not sure why i got the following error: "img = cv2.imread(file) TypeError: Can't convert object to 'str' for 'filename'" – Mohamed Oct 07 '22 at 04:11
  • My bad! It's because `file` is a `pathlib.Path` object, so it needs to be cast to a `str` - another oversight of mine. I'll update my answer, which should instead use the line `img = cv2.imread(str(file))`. – Raj K Oct 07 '22 at 05:09
  • Also, this may be a bit unrelated, but I believe mediapipe has a dedicated "video mode" that uses landmarks from past frames to better predict landmarks in future frames. If you're interested, I'd be happy to help you modify your code to use it. See https://google.github.io/mediapipe/solutions/face_mesh#python-solution-api for more details. – Raj K Oct 07 '22 at 05:22
  • not sure why some of the subfolders do not return any texts, also what can be modified to print all the data in just one text file? – Mohamed Oct 07 '22 at 07:11
  • I'll leave that debugging to you, as I think this discussion is straying a bit from the original question. If you have other questions, please feel free to make new posts on SO! – Raj K Oct 07 '22 at 21:49