Browse Source

frame differencing approaches (generally not successful)

Felix Kleinsteuber 3 years ago
parent
commit
9d8148f751
4 changed files with 155 additions and 27 deletions
  1. 43 7
      frame_differencing.ipynb
  2. 23 0
      py/ImageUtils.py
  3. 57 8
      py/Session.py
  4. 32 12
      scan_sessions.ipynb

File diff suppressed because it is too large
+ 43 - 7
frame_differencing.ipynb


+ 23 - 0
py/ImageUtils.py

@@ -1,5 +1,6 @@
 from datetime import datetime
 from PIL import Image
+import matplotlib.pyplot as plt
 
 def get_image_date(img_path: str) -> datetime:
     """Returns the date from the image EXIF data.
@@ -13,3 +14,25 @@ def get_image_date(img_path: str) -> datetime:
     img = Image.open(img_path)
     date_raw = img.getexif()[306]
     return datetime.strptime(date_raw, "%Y:%m:%d %H:%M:%S")
+
+def display_images(images: list, titles: list, colorbar=False, size=8, **imshowargs):
+    """Displays the given images next to each other.
+
+    Args:
+        images (list of np.ndarray): list of image arrays
+        titles (list of str): list of titles
+        colorbar (bool, optional): Display colorbars. Defaults to False.
+        size (int, optional): plt size per image. Defaults to 8.
+    """
+    numImgs = len(images)
+    plt.figure(figsize=(numImgs * size, size))
+    for i, image, title in zip(range(numImgs), images, titles):
+        plt.subplot(1, numImgs, i + 1)
+        plt.imshow(image, **imshowargs)
+        plt.title(title)
+        if colorbar:
+            plt.colorbar()
+    plt.tight_layout()
+    plt.show()
+
+

+ 57 - 8
py/Session.py

@@ -5,9 +5,11 @@ import subprocess
 from warnings import warn
 import os
 from tqdm import tqdm
+import matplotlib.image as mpimg
+from skimage import transform, io
 
 from py.FileUtils import list_folders, list_jpegs_recursive, verify_expected_subfolders
-from py.ImageUtils import get_image_date
+from py.ImageUtils import display_images, get_image_date
 
 class Session:
     def __init__(self, folder: str):
@@ -130,14 +132,18 @@ class Session:
 
     def get_motion_image_from_filename(self, filename: str) -> "MotionImage":
         if filename in self.motion_dates:
-            return MotionImage(self, filename)
+            return MotionImage(self, filename, self.motion_dates[filename])
         else:
             raise ValueError(f"Unknown motion file name: {filename}")
     
-    def get_random_motion_image(self) -> "MotionImage":
+    def get_random_motion_image(self, day_only=False, night_only=False) -> "MotionImage":
         if len(self.motion_dates) == 0:
             raise ValueError("No motion images in session!")
-        return MotionImage(self, random.choice(list(self.motion_dates.keys())))
+        img = None
+        while img is None or (day_only and img.is_nighttime()) or (night_only and img.is_daytime()):
+            filename = random.choice(list(self.motion_dates.keys()))
+            img = MotionImage(self, filename, self.motion_dates[filename])
+        return img
     
     def get_closest_lapse_images(self, motion_file: str):
         date: datetime = self.motion_dates[motion_file]
@@ -151,12 +157,13 @@ class Session:
             warn(f"There are multiple lapse images for date {previous_date}! Choosing the first one.")
         if len(self.lapse_map[next_date]) > 1:
             warn(f"There are multiple lapse images for date {next_date}! Choosing the first one.")
-        return LapseImage(self, self.lapse_map[previous_date][0]), LapseImage(self, self.lapse_map[next_date][0])
+        return LapseImage(self, self.lapse_map[previous_date][0], previous_date), LapseImage(self, self.lapse_map[next_date][0], next_date)
 
 class MotionImage:
-    def __init__(self, session: Session, filename: str):
+    def __init__(self, session: Session, filename: str, date: datetime):
         self.session = session
         self.filename = filename
+        self.date = date
         if not self.filename in session.motion_dates:
             raise ValueError(f"File name {filename} not in session!")
         if not os.path.isfile(self.get_full_path()):
@@ -170,13 +177,39 @@ class MotionImage:
         print(f"Opening {full_path}...")
         subprocess.call(("xdg-open", full_path))
 
+    def read(self, truncate_y = (40, 40), scale=1, gray=True):
+        full_path = self.get_full_path()
+        img = io.imread(full_path, as_gray=gray)
+        # truncate
+        if truncate_y is not None:
+            if truncate_y[0] > 0 and truncate_y[1] > 0:
+                img = img[truncate_y[0]:(-truncate_y[1]),:]
+            elif truncate_y[0] > 0:
+                img = img[truncate_y[0]:,:]
+            elif truncate_y[1] > 0:
+                img = img[:(-truncate_y[1]),:]
+        # scale
+        if scale is not None and scale < 1:
+            img = transform.rescale(img, scale)
+        return img
+
     def get_closest_lapse_images(self):
-        return self.session.get_closest_lapse_images(self.filename)
+        before, after = self.session.get_closest_lapse_images(self.filename)
+        # rel = 0 if motion image was taken at before lapse image, rel = 1 if motion image was taken at after lapse image
+        rel = (self.date - before.date).total_seconds() / (after.date - before.date).total_seconds()
+        return before, after, rel
+
+    def is_daytime(self):
+        return 6 <= self.date.hour <= 18
+    
+    def is_nighttime(self):
+        return not self.is_daytime()
         
 class LapseImage:
-    def __init__(self, session: Session, filename: str):
+    def __init__(self, session: Session, filename: str, date: datetime):
         self.session = session
         self.filename = filename
+        self.date = date
         if not self.filename in session.lapse_dates:
             raise ValueError(f"File name {filename} not in session!")
         if not os.path.isfile(self.get_full_path()):
@@ -189,4 +222,20 @@ class LapseImage:
         full_path = self.get_full_path()
         print(f"Opening {full_path}...")
         subprocess.call(("xdg-open", full_path))
+
+    def read(self, truncate_y = (40, 40), scale=1, gray=True):
+        full_path = self.get_full_path()
+        img = io.imread(full_path, as_gray=gray)
+        # truncate
+        if truncate_y is not None:
+            if truncate_y[0] > 0 and truncate_y[1] > 0:
+                img = img[truncate_y[0]:(-truncate_y[1]),:]
+            elif truncate_y[0] > 0:
+                img = img[truncate_y[0]:,:]
+            elif truncate_y[1] > 0:
+                img = img[:(-truncate_y[1]),:]
+        # scale
+        if scale is not None and scale < 1:
+            img = transform.rescale(img, scale)
+        return img
         

+ 32 - 12
scan_sessions.ipynb

@@ -81,20 +81,20 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 67,
+   "execution_count": 70,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Session 'Marten_01' at folder: /home/AMMOD_data/camera_traps/BayerWald/Vielkadaver-Projekt/VIELAAS_Spring_Session01-VIELAAS_Marten_01\n",
+      "Session 'Raccoon_05' at folder: /home/AMMOD_data/camera_traps/BayerWald/Vielkadaver-Projekt/VIELAAS_Spring_Session05-VIELAAS_Raccoon_05\n",
       "Loaded scans.\n"
      ]
     }
    ],
    "source": [
-    "session = ds.create_session(\"Marten_01\")"
+    "session = ds.create_session(\"Raccoon_05\")"
    ]
   },
   {
@@ -170,24 +170,24 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 53,
+   "execution_count": 71,
    "metadata": {},
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "100%|██████████| 578/578 [00:00<00:00, 40275.58it/s]"
+      "100%|██████████| 1088/1088 [00:00<00:00, 41156.23it/s]"
      ]
     },
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "* 578 lapse dates\n",
-      "* 48 duplicates\n",
+      "* 1088 lapse dates\n",
+      "* 1 duplicates\n",
       "* 0 multiples (more than two files per date)\n",
-      "* 0 deviant duplicates: []\n"
+      "* 1 deviant duplicates: [datetime.datetime(2021, 6, 5, 14, 0)]\n"
      ]
     },
     {
@@ -202,6 +202,26 @@
     "total, total_duplicates, _, deviants = session.check_lapse_duplicates()"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 72,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[datetime.datetime(2021, 6, 5, 14, 0)]"
+      ]
+     },
+     "execution_count": 72,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "deviants"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -211,20 +231,20 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 68,
+   "execution_count": 73,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "#1 /home/AMMOD_data/camera_traps/BayerWald/Vielkadaver-Projekt/VIELAAS_Spring_Session01-VIELAAS_Marten_01/Lapse/2021_06_22_09h_5050.jpg\n",
-      "#2 /home/AMMOD_data/camera_traps/BayerWald/Vielkadaver-Projekt/VIELAAS_Spring_Session01-VIELAAS_Marten_01/Lapse/2021_06_22_09h_5348.jpg\n"
+      "#1 /home/AMMOD_data/camera_traps/BayerWald/Vielkadaver-Projekt/VIELAAS_Spring_Session05-VIELAAS_Raccoon_05/Lapse/2021_06_05_14h_517.jpg\n",
+      "#2 /home/AMMOD_data/camera_traps/BayerWald/Vielkadaver-Projekt/VIELAAS_Spring_Session05-VIELAAS_Raccoon_05/Lapse/2021_06_05_14h_518.jpg\n"
      ]
     }
    ],
    "source": [
-    "session.open_images_for_date(datetime(2021, 6, 22, 9, 0))"
+    "session.open_images_for_date(datetime(2021, 6, 5, 14, 0))"
    ]
   },
   {

Some files were not shown because too many files changed in this diff