Python: ค้นหาจำนวนลายมือในวิดีโอ

Aug 30 2020

คุณรู้จักอัลกอริทึมที่สามารถดูว่ามีการเขียนด้วยลายมือบนรูปภาพหรือไม่? ฉันไม่สนใจที่จะรู้ว่าลายมือเขียนว่าอย่างไร แต่มีเพียงของขวัญชิ้นเดียวเท่านั้น?

ฉันมีวิดีโอของใครบางคนที่กรอกสไลด์ด้วยลายมือ เป้าหมายของฉันคือการกำหนดจำนวนสไลด์ที่เต็มไปด้วยลายมือแล้ว

สามารถดาวน์โหลดวิดีโอที่เป็นปัญหาได้ที่นี่: http://www.filedropper.com/00_6

สำหรับวิดีโอนี้มีการแนะนำวิธีแก้ปัญหาที่ยอดเยี่ยมไว้แล้วในQuantify ว่าสไลด์นั้นเต็มไปด้วยลายมือเท่าใด

วิธีแก้ปัญหาขึ้นอยู่กับการสรุปจำนวนสีเฉพาะที่ใช้สำหรับการเขียนด้วยลายมือ อย่างไรก็ตามหากลายมือไม่ได้เป็นสีน้ำเงิน แต่มีสีอื่นที่สามารถพบได้ในสิ่งที่ไม่ใช่ลายมือด้วยวิธีนี้จะไม่ได้ผล

ดังนั้นฉันจึงสนใจที่จะทราบว่ามีวิธีแก้ปัญหาทั่วไปเพิ่มเติมเพื่อตรวจสอบว่ามีลายมือปรากฏอยู่บนรูปภาพหรือไม่?

สิ่งที่ฉันได้ทำไปแล้ว:ฉันกำลังคิดที่จะแยกรูปทรงของภาพจากนั้นก็ตรวจจับส่วนที่เขียนด้วยลายมือตามความโค้งของรูปทรง (แต่ฉันไม่รู้ว่าจะทำส่วนนั้นอย่างไร) มันอาจจะไม่ใช่ความคิดที่ดีที่สุด แต่มันก็ไม่ถูกต้องเสมอไป ...

import cv2
import matplotlib.pyplot as plt



img = 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) 

คำตอบ

9 NicolasGervais Sep 04 2020 at 11:50

คุณสามารถระบุพื้นที่ที่ถ่ายด้วยการเขียนด้วยมือโดยการกำบังพิกเซลจากเทมเพลตจากนั้นทำเช่นเดียวกันเพื่อให้เกิดความแตกต่างระหว่างเฟรมต่อไปและเทมเพลต คุณสามารถใช้การขยายช่องเปิดและขีด จำกัด สำหรับสิ่งนี้

เริ่มต้น Let 's กับคุณแม่แบบ มาระบุส่วนที่เราจะปกปิด:

import cv2
import numpy as np

template = 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)

จากนั้นเราจะกำหนดเกณฑ์เพื่อเปลี่ยนเป็นหน้ากากขาวดำ:

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

ในเฟรมต่อมาเราจะลบมาสก์นี้ออกจากรูปภาพโดยเปลี่ยนพิกเซลทั้งหมดให้เป็นสีขาว ตัวอย่างเช่น:

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 += 1

mask = np.where(thresh == 0)

example = frames[300]
example[mask] = [255, 255, 255]
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] = 255
    closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    return closing

cv2.imshow('', difference_with_mask(frames[400]))
cv2.waitKey(0)

เพื่อจัดการกับความจริงที่ว่าคุณไม่ต้องการให้มือถูกตรวจพบว่าเป็นการเขียนด้วยมือฉันขอแนะนำว่าแทนที่จะใช้มาสก์สำหรับแต่ละเฟรมคุณใช้เปอร์เซ็นไทล์ที่ 95 ของ 15 เฟรมที่ 30 สุดท้าย ... ดูนี่สิ:

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)

ตอนนี้เฟรมตัวอย่างกลายเป็นแบบนี้ (มือจะถูกลบออกเพราะส่วนใหญ่ไม่ได้อยู่ใน 15 เฟรมสุดท้ายที่ 30):

ดังที่คุณเห็นบางส่วนของการเขียนด้วยมือหายไป มันจะมาในภายหลังเนื่องจากการเปลี่ยนแปลงเปอร์เซ็นไทล์ขึ้นอยู่กับเวลาที่เรากำลังทำอยู่ คุณจะเห็นในภายหลัง: ในตัวอย่างของฉันที่มีเฟรม 18,400 ข้อความที่ขาดหายไปในภาพด้านบนจะปรากฏขึ้น จากนั้นคุณสามารถใช้ฟังก์ชันที่ฉันให้คุณและนี่จะเป็นผลลัพธ์:

และไปเลย! โปรดทราบว่าโซลูชันนี้ซึ่งไม่รวมถึงมือจะใช้เวลาคำนวณนานขึ้นเนื่องจากมีการคำนวณบางอย่างที่ต้องทำ การใช้เพียงภาพโดยไม่คำนึงถึงมือจะคำนวณได้ทันทีถึงขนาดที่คุณสามารถเรียกใช้บนฟีดเว็บแคมได้แบบเรียลไทม์

ตัวอย่างสุดท้าย:

นี่คือเฟรม 18,400:

ภาพสุดท้าย:

คุณสามารถเล่นกับฟังก์ชั่นนี้ได้หากต้องการให้มาสก์ล้อมรอบข้อความให้บางมากขึ้น:

รหัสเต็ม:

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 += 1

template = 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] = 255
    closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    return closing


cv2.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:
        break


cv2.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)
4 kavko Sep 05 2020 at 08:14

คุณสามารถลองสร้างเทมเพลตก่อนที่จะตรวจพบซึ่งคุณสามารถใช้เพื่อหักออกจากเฟรมปัจจุบันของวิดีโอได้ วิธีหนึ่งที่คุณสามารถสร้างเทมเพลตดังกล่าวได้คือการวนซ้ำทุกพิกเซลของเฟรมและค้นหาว่ามีค่า (สีขาว) ในพิกัดนั้นสูงกว่าค่าที่เก็บไว้ในรายการหรือไม่

นี่คือตัวอย่างเทมเพลตดังกล่าวจากวิดีโอของคุณโดยการวนซ้ำในสองวินาทีแรก:

เมื่อคุณมีแล้วคุณสามารถตรวจจับข้อความได้อย่างง่ายดาย คุณสามารถใช้cv2.absdiff()ฟังก์ชันเพื่อสร้างความแตกต่างของเทมเพลตและเฟรม นี่คือตัวอย่าง:

เมื่อคุณมีภาพนี้แล้วการค้นหาการเขียน (threshold + contour search หรือสิ่งที่คล้ายกัน) เป็นเรื่องเล็กน้อย

นี่คือตัวอย่างรหัส:

import numpy as np
import cv2

cap = cv2.VideoCapture('0_0.mp4')  # read video

bgr = cap.read()[1]  # get first frame
frame = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)  # transform to grayscale
template = frame.copy()  # make a copy of the grayscale

h, w = frame.shape[:2]  # height, width

matrix = []  # a list for [y, x] coordinares
# fill matrix with all coordinates of the image (height x width)
for j in range(h):
    for i in range(w):
        matrix.append([j, i])

fps = cap.get(cv2.CAP_PROP_FPS)  # frames per second of the video
seconds = 2  # How many seconds of the video you wish to look the template for
k = seconds * fps  # calculate how many frames of the video is in that many seconds
i = 0  # some iterator to count the frames
lowest = []  # list that will store highest values of each pixel on the fram - that will build our template

# store the value of the first frame - just so you can compare it in the next step
for j in matrix:
    y = j[0]
    x = j[1]
    lowest.append(template[y, x])

# loop through the number of frames calculated before
while(i < k):
    bgr = cap.read()[1]  # bgr image
    frame = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)  # transform to grayscale
    # iterate through every pixel (pixels are located in the matrix)
    for l, j in enumerate(matrix):
        y = j[0]  # x coordinate
        x = j[1]  # y coordinate
        temp = template[y, x]  # value of pixel in template
        cur = frame[y, x]  # value of pixel in the current frame
        if cur > temp:  # if the current frame has higher value change the value in the "lowest" list
            lowest[l] = cur
    i += 1  # increment the iterator

    # just for vizualization
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

i = 0  # new iteratir to increment position in the "lowest" list
template = np.ones((h, w), dtype=np.uint8)*255  #  new empty white image
# iterate through the matrix and change the value of the new empty white image to that value
# in the "lowest" list
for j in matrix:
    template[j[0], j[1]] = lowest[i]
    i += 1

# just for visualization - template
cv2.imwrite("template.png", template)
cv2.imshow("template", template)
cv2.waitKey(0)
cv2.destroyAllWindows()

counter = 0  # counter of countours: logicaly if the number of countours would
# rapidly decrease than that means that a new template is in order
mean_compare = 0  # this is needed for a simple color checker if the contour is
# the same color as the oders
# this is the difference between the frame of the video and created template
while(cap.isOpened()):
    bgr = cap.read()[1]  # bgr image
    frame = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)  # grayscale
    img = cv2.absdiff(template, frame)  # resulted difference
    thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]  # thresholded image
    kernel = np.ones((5, 5), dtype=np.uint8)  # simple kernel
    thresh = cv2.dilate(thresh, kernel, iterations=1)  # dilate thresholded image
    cnts, h = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # contour search
    if len(cnts) < counter*0.5 and counter > 50:  # check if new template is in order
        # search for new template again
        break
    else:
        counter = len(cnts) # update counter
        for cnt in cnts:  # iterate through contours
            size = cv2.contourArea(cnt)  # size of contours - to filter out noise
            if 20 < size < 30000:  # noise criterion
                mask = np.zeros(frame.shape, np.uint8)  # empry mask - needed for color compare
                cv2.drawContours(mask, [cnt], -1, 255, -1)  # draw contour on mask
                mean = cv2.mean(bgr, mask=mask)  # the mean color of the contour

                if not mean_compare:  # first will set the template color
                    mean_compare = mean
                else:
                    k1 = 0.85  # koeficient how much each channels value in rgb image can be smaller
                    k2 = 1.15 # koeficient how much each channels value in rgb image can be bigger
                    # condition
                    b = bool(mean_compare[0] * k1 < mean[0] < mean_compare[0] * k2)
                    g = bool(mean_compare[1] * k1 < mean[1] < mean_compare[1] * k2)
                    r = bool(mean_compare[2] * k1 < mean[2] < mean_compare[2] * k2)
                    if b and g and r:
                        cv2.drawContours(bgr, [cnt], -1, (0, 255, 0), 2)  # draw on rgb image

    # just for visualization
    cv2.imshow('img', bgr)
    if cv2.waitKey(1) & 0xFF == ord('s'):
        cv2.imwrite(str(j)+".png", img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# release the video object and destroy window
cap.release()
cv2.destroyAllWindows()

ผลลัพธ์ที่เป็นไปได้อย่างหนึ่งด้วยตัวกรองขนาดและสีที่เรียบง่าย:

หมายเหตุ: อัลกอริทึมการค้นหาเทมเพลตนี้ช้ามากเนื่องจากลูปที่ซ้อนกันและอาจได้รับการปรับให้เหมาะสมเพื่อให้เร็วขึ้นคุณต้องมีความรู้ทางคณิตศาสตร์มากกว่าฉันเล็กน้อย นอกจากนี้คุณจะต้องตรวจสอบว่าเทมเพลตมีการเปลี่ยนแปลงในวิดีโอเดียวกันหรือไม่ฉันเดาว่าไม่น่าจะยากเกินไป

แนวคิดที่ง่ายกว่าในการทำให้เร็วขึ้นเล็กน้อยคือการปรับขนาดเฟรมเพื่อสมมติว่า 20% และทำการค้นหาเทมเพลตเดียวกัน หลังจากนั้นปรับขนาดกลับเป็นต้นฉบับและขยายเทมเพลต ผลลัพธ์จะไม่ดีเท่า แต่จะสร้างมาสก์ว่าข้อความและเส้นของเทมเพลตอยู่ที่ไหน จากนั้นวาดลงบนเฟรม

1 hrokr Sep 01 2020 at 21:09

ฉันไม่คิดว่าคุณต้องการรหัสจริงๆในกรณีนี้และมันจะค่อนข้างนานถ้าคุณทำ แต่นี่คืออัลกอริทึมที่ต้องทำ

ใช้โมเดล EAST (ตัวตรวจจับข้อความฉากที่แม่นยำอย่างมีประสิทธิภาพ) ของ OpenCV ที่จุดเริ่มต้นเพื่อสร้างข้อความเริ่มต้นบนสไลด์ นั่นทำให้คุณมีกรอบขอบเขตของเปอร์เซ็นต์เริ่มต้นของสไลด์ที่ปกคลุมไปด้วยข้อความสไลด์ซึ่งต่างจากข้อความอธิบายที่เขียนด้วยลายมือ

ทุกครั้งพูด 1-5 วินาที (คนเขียนไม่ได้เร็วขนาดนั้น) เปรียบเทียบภาพพื้นฐานนั้นกับภาพปัจจุบันและภาพก่อนหน้า

  • หากรูปภาพปัจจุบันมีข้อความมากกว่ารูปภาพก่อนหน้า แต่กรอบขอบเขตเริ่มต้นไม่เหมือนกันแสดงว่าคุณมีสไลด์ใหม่และค่อนข้างยุ่ง

  • หากรูปภาพปัจจุบันมีข้อความมากกว่าภาพก่อนหน้า แต่กล่องขอบเขตเริ่มต้นเหมือนกันระบบจะเพิ่มข้อความเพิ่มเติม

  • หากรูปภาพปัจจุบันมีข้อความน้อยกว่ารูปภาพก่อนหน้า แต่กรอบขอบเขตเริ่มต้นไม่เหมือนกันคุณจะมีสไลด์ใหม่อีกครั้ง - เท่านั้นไม่ยุ่งและมีพื้นที่ว่างเหมือนภาพสุดท้ายที่จะเขียน

  • หากรูปภาพปัจจุบันมีข้อความน้อยกว่ารูปภาพก่อนหน้า แต่กรอบขอบเขตเริ่มต้นเหมือนกันคุณอาจมีสไลด์ที่ซ้ำกันซึ่งน่าจะเป็นข้อความมากกว่านั้นหรือครูกำลังลบส่วนเพื่อดำเนินการต่อหรือแก้ไขคำอธิบาย หมายความว่าคุณจะต้องมีวิธีจัดการกับสิ่งนี้

เมื่อคุณมีสไลด์ใหม่ให้นำรูปภาพก่อนหน้านี้และเปรียบเทียบกรอบขอบเขตของข้อความทั้งหมดลบกล่องสำหรับสถานะเริ่มต้น

ในทางคำนวณสิ่งนี้จะไม่ถูก (แน่นอนคุณจะไม่ทำชีวิตนี้อย่างน้อยก็ไม่เป็นเวลาหลายปี) แต่มันแข็งแกร่งและการสุ่มตัวอย่างข้อความทุก ๆ วินาทีจะช่วยได้

โดยส่วนตัวฉันจะเข้าใกล้สิ่งนี้เป็นวงดนตรี นั่นคือกล่องขอบเขตเริ่มต้นจากนั้นดูสีของข้อความ หากคุณสามารถหลีกเลี่ยงเปอร์เซ็นต์ของข้อความสีต่างๆได้ให้ทำ และเมื่อทำไม่ได้คุณก็ยังสบายดี

1 MH304 Sep 06 2020 at 14:42

นอกจากคำตอบที่ดีที่ผู้คนให้มาแล้วฉันยังมีคำแนะนำอีกสองอย่าง

วิธีแรกคือวิธีการของ CNN การใช้รูทีนการตรวจจับวัตถุบางอย่างสามารถทำได้โดยสิ้นเชิงหรือแม้แต่วิธีการแบ่งส่วน (เช่น U-NET) เพื่อแยกความแตกต่างระหว่างข้อความ เป็นเรื่องง่ายเพราะคุณสามารถค้นหารูปภาพนับล้านจากหนังสือข้อความดิจิทัลและเอกสารที่เขียนด้วยลายมือเพื่อฝึกโมเดลของคุณ

แนวทางที่สองคือการค้นหาและแยกทุกสัญลักษณ์บนภาพแยกกัน (ด้วยวิธีง่ายๆเช่นเดียวกับที่คุณใช้จนถึงตอนนี้หรือด้วยconnectedcomponent) เนื่องจากตัวอักษรและสัญลักษณ์การพิมพ์มีรูปร่างและลักษณะที่เป็นเอกลักษณ์ (แบบอักษรที่คล้ายกัน - ไม่เหมือนกับตัวอักษรที่เขียนด้วยลายมือ) คุณจึงสามารถจับคู่ตัวอักษรที่พบทั้งหมดกับตัวอักษรพิมพ์ตัวอย่างที่คุณรวบรวมแยกกันเพื่อแยกความแตกต่างระหว่างตัวอักษรที่เขียนด้วยลายมือและตัวพิมพ์ การจับคู่ตามจุดคุณลักษณะ (เช่น SURF) อาจเป็นเครื่องมือที่ดีสำหรับแนวทางนี้