Thanks @badger101 - at the last run of this workflow it found around 70,000 movie clips to process!
I’ve augmented it a bit with an added piece where I highlight the differences between the 0th frame and each other, along with the number of distinct change areas, the total changing area and the proportion of the image changing using opencv (I did this in a second python snippet using the frames output from the first - I’m sure it will be more efficient to simply merge into one snippet eventually!):
(This is in a python snippet with 2 input ports - the first has the 0th frame for the movie, the 2nd all the others)
# Copy input to output
output_table_1 = input_table_2.copy()
import cv2
import imutils
from skimage.metrics import structural_similarity as compare_ssim
import numpy as np
#Load the base image
imgBase = cv2.imread(input_table_1['frames'][0])
imgBaseGrey = cv2.cvtColor(imgBase, cv2.COLOR_BGR2GRAY)
minContourSize = flow_variables['minDiffArea']
scores = []
numDiffs = []
areas = []
comps = []
diffs = []
thresholds = []
totalAreas = []
pcChanges = []
for i in range(len(input_table_2['frames'])):
imgComp = cv2.imread(input_table_2['frames'][i])
imgCompGrey = cv2.cvtColor(imgComp, cv2.COLOR_BGR2GRAY)
# compute the Structural Similarity Index (SSIM) between the two
# images, ensuring that the difference image is returned
(score, diff) = compare_ssim(imgBaseGrey, imgCompGrey, full=True)
diff = (diff * 255).astype("uint8")
scores.append(score)
# threshold the difference image, followed by finding contours to
# obtain the regions of the two input images that differ
thresh = cv2.threshold(diff, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# Create a mask and draw the contour bounding boxes on it
mask = np.zeros(imgComp.shape[:2],dtype=np.uint8)
for c in cnts:
if cv2.contourArea(c) > minContourSize:
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(mask, (x, y), (x + w, y + h), (255), -1)
# Now re-countour on the mask
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
numDiffs.append(len(cnts))
# loop over the contours
imgAreas = []
for c in cnts:
imgAreas.append(cv2.contourArea(c))
cv2.drawContours(imgComp, [c], 0, (0, 0, 255), 2)
areas.append(imgAreas)
totalArea = sum(imgAreas)
totalAreas.append(totalArea)
pcChanges.append(100.0 * totalArea / np.prod(imgComp.shape[:2]))
# show the output images
outPath = input_table_2['frames'][i].replace("."+flow_variables['imgFormat'], "_")
compPath = outPath +"comp."+flow_variables['imgFormat']
cv2.imwrite(compPath, imgComp)
comps.append(compPath)
output_table_1['Scores']=scores
output_table_1['Number of Diffs'] = numDiffs
output_table_1['Areas'] = areas
output_table_1['Total Area'] = totalAreas
output_table_1['% Change'] = pcChanges
output_table_1['Boxed'] = comps
The inspiration for the above came from 2 really helpful sources:
Firstly, Image Difference with OpenCV and Python - PyImageSearch showed how to compare and add a simple bounding rectangle - but sometimes there were overlapping rectangles, and rectangles within other rectangles.
The second showed how to combine the rectangles neatly and relatively efficiently - python - OpenCV join contours when rectangle overlaps another rect - Stack Overflow
I can then filter the number of frames I have to look at - those with large numbers of changing areas or a high overall %age change are generally things like the light catching the background or the wind blowing bushes…
Who knows, maybe I will add in something like this next - Detecting animals in the backyard — practical application of deep learning. | by Gaiar Baimuratov | Towards Data Science ?!
Steve