Python: Find Amount of Handwriting in Video

2024/9/25 0:40:20

Do you know of an algorithm that can see that there is handwriting on an image? I am not interested in knowing what the handwriting says, but only that there is one present?

I have a video of someone filling a slide with handwriting. My goal is to determine how much of the slide has been filled with handwriting already.

enter image description here

The video in question can be downloaded here: http://www.filedropper.com/00_6

For this particular video, a great solution was already suggested in Quantify how much a slide has been filled with handwriting

The solution is based on summing the amount of the specific color used for the handwriting. However, if the handwriting is not in blue but any other color that can also be found on non-handwriting, this approach will not work.

Therefore, I am interested to know, if there exists a more general solution to determine if there is handwriting present on an image?

What I have done so far: I was thinking of extracting the contours of an image, and then somehow detect the handwriting part based on how curvy the contours are (but I have no clue how to do that part). it might not be the best idea, though, as again it's not always correct...

import cv2
import matplotlib.pyplot as pltimg = cv2.imread(PATH TO IMAGE)
print("img shape=", img.shape)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)cv2.imshow("image", gray)
cv2.waitKey(1)
#### extract all contours
# Find Canny edges 
edged = cv2.Canny(gray, 30, 200) 
cv2.waitKey(0) # Finding Contours 
# Use a copy of the image e.g. edged.copy() 
# since findContours alters the image 
contours, hierarchy = cv2.findContours(edged,  cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) cv2.imshow('Canny Edges After Contouring', edged) 
cv2.waitKey(0) print("Number of Contours found = " + str(len(contours))) # Draw all contours 
# -1 signifies drawing all contours 
cv2.drawContours(img, contours, -1, (0, 255, 0), 3) cv2.imshow('Contours', img) 
cv2.waitKey(0) 
Answer

You can identify the space taken by hand-writing by masking the pixels from the template, and then do the same for the difference between further frames and the template. You can use dilation, opening, and thresholding for this.

Let's start with your template. Let's identify the parts we will mask:

import cv2
import numpy as nptemplate = cv2.imread('template.jpg')

enter image description here

Now, let's broaden the occupied pixels to make a zone that we will mask (hide) later:

template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
kernel = np.ones((5, 5),np.uint8)
dilation = cv2.dilate(255 - template, kernel,iterations = 5)

enter image description here Then, we will threshold to turn this into a black and white mask:

_, thresh = cv2.threshold(dilation,25,255,cv2.THRESH_BINARY_INV)

enter image description here

In later frames, we will subtract this mask from the picture, by turning all these pixels to white. For instance:

import numpy as np
import cv2
vidcap = cv2.VideoCapture('0_0.mp4')
success,image = vidcap.read()
count = 0
frames = []while count < 500:frames.append(image)success,image = vidcap.read()count += 1mask = np.where(thresh == 0)example = frames[300]
example[mask] = [255, 255, 255]
cv2.imshow('', example)
cv2.waitKey(0)

enter image description here

Now, we will create a function that will return the difference between the template and a given picture. We will also use opening to get rid of the left over single pixels that would make it ugly.

def difference_with_mask(image):grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)kernel = np.ones((5, 5), np.uint8)dilation = cv2.dilate(255 - grayscale, kernel, iterations=5)_, thresh = cv2.threshold(dilation, 25, 255, cv2.THRESH_BINARY_INV)thresh[mask] = 255closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)return closingcv2.imshow('', difference_with_mask(frames[400]))
cv2.waitKey(0)

enter image description here

To address the fact that you don't want to have the hand detected as hand-writing, I suggest that instead of using the mask for every individual frame, you use the 95th percentile of the 15 last 30th frame... hang on. Look at this:

results = []
for ix, frame in enumerate(frames):if ix % 30 == 0:history.append(frame)results.append(np.quantile(history, 0.95, axis=0))print(ix)

Now, the example frame becomes this (the hand is removed because it wasn't mostly present in the 15 last 30th frames):

enter image description here

As you can see a little part of the hand-writing is missing. It will come later, because of the time-dependent percentile transformation we're doing. You'll see later: in my example with frame 18,400, the text that is missing in the image above is present. Then, you can use the function I gave you and this will be the result:

enter image description here

And here we go! Note that this solution, which doesn't include the hand, will take longer to compute because there's a few calculations needing to be done. Using just an image with no regard to the hand would calculate instantly, to the extent that you could probably run it on your webcam feed in real time.

Final Example:

Here's the frame 18,400:

enter image description here

Final image:

enter image description here

You can play with the function if you want the mask to wrap more thinly around the text:

enter image description here

Full code:

import os
import numpy as np
import cv2
vidcap = cv2.VideoCapture('0_0.mp4')
success,image = vidcap.read()
count = 0
from collections import deque
frames = deque(maxlen=700)while count < 500:frames.append(image)success,image = vidcap.read()count += 1template = cv2.imread('template.jpg')
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
kernel = np.ones((5, 5),np.uint8)
dilation = cv2.dilate(255 - template, kernel,iterations = 5)cv2.imwrite('dilation.jpg', dilation)
cv2.imshow('', dilation)
cv2.waitKey(0)_, thresh = cv2.threshold(dilation,25,255,cv2.THRESH_BINARY_INV)
cv2.imwrite('thresh.jpg', thresh)
cv2.imshow('', thresh)
cv2.waitKey(0)mask = np.where(thresh == 0)example = frames[400]
cv2.imwrite('original.jpg', example)
cv2.imshow('', example)
cv2.waitKey(0)example[mask] = 255
cv2.imwrite('example_masked.jpg', example)
cv2.imshow('', example)
cv2.waitKey(0)def difference_with_mask(image):grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)kernel = np.ones((5, 5), np.uint8)dilation = cv2.dilate(255 - grayscale, kernel, iterations=5)_, thresh = cv2.threshold(dilation, 25, 255, cv2.THRESH_BINARY_INV)thresh[mask] = 255closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)return closingcv2.imshow('', difference_with_mask(frames[400]))
cv2.waitKey(0)masked_example = difference_with_mask(frames[400])
cv2.imwrite('masked_example.jpg', masked_example)from collections import deque
history = deque(maxlen=15)results = []
for ix, frame in enumerate(frames):if ix % 30 == 0:history.append(frame)results.append(np.quantile(history, 0.95, axis=0))print(ix)if ix > 500:breakcv2.imshow('', frames[400])
cv2.waitKey(0)cv2.imshow('', results[400].astype(np.uint8))
cv2.imwrite('percentiled_frame.jpg', results[400].astype(np.uint8))
cv2.waitKey(0)cv2.imshow('', difference_with_mask(results[400].astype(np.uint8)))
cv2.imwrite('final.jpg', difference_with_mask(results[400].astype(np.uint8)))
cv2.waitKey(0)
https://en.xdnf.cn/q/71560.html

Related Q&A

Include nonce and block count in PyCrypto AES MODE_CTR

Some background information, you can skip this part for the actual questionthis is my third question about this topic here at stackoverflow. To be complete, these are the other questions AES with crypt…

Why is pandas.series.map so shockingly slow?

Some days I just hate using middleware. Take this for example: Id like to have a lookup table that maps values from a set of inputs (domain) values, to outputs (range) values. The mapping is unique. A …

Viewset create custom assign value in Django Rest Framework

Would like to set a CustomUsers username by using the input email, but where to do the custom assigning, in view? At the same time it receiving a file as well.Models.pyclass CustomUser(AbstractUser):a…

Remove a relation many-to-many (association object) on Sqlalchemy

Im stuck with a SqlAlchemy problem.I just want to delete an relation. This relation is made by an association object.modelsclass User(db.Model, UserMixin):id = db.Column(db.Integer, pr…

Spark Dataframes: Skewed Partition after Join

Ive two dataframes, df1 with 22 million records and df2 with 2 million records. Im doing the right join on email_address as a key. test_join = df2.join(df1, "email_address", how = right).cach…

Caught TypeError while rendering: __init__() got an unexpected keyword argument use_decimal

While running the program i am getting the following error messageCaught TypeError while rendering: __init__() got an unexpected keyword argument use_decimalHere is my code i am using jquery 1.6.4 d…

How to get chunks of elements from a queue?

I have a queue from which I need to get chunks of 10 entries and put them in a list, which is then processed further. The code below works (the "processed further" is, in the example, just pr…

Receiving commandline input while listening for connections in Python

I am trying to write a program that has clients connect to it while the server is still able to send commands to all of the clients. I am using the "Twisted" solution. How can I go about this…

Passing a parameter through AJAX URL with Django

Below is my code. n logs correctly in the console, and everything works perfectly if I manually enter the value for n into url: {% url "delete_photo" iddy=2%}. Alas, when I try to use n as a …

WARNING: toctree contains reference to nonexisting document error with Sphinx

I used the sphinx-quickstart to set everything up. I used doc/ for the documentation root location. The folder containing my package is setup as: myfolder/doc/mypackage/__init__.pymoprob.py...After the…