You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在OpenCV中获取视频内运动事件的实际播放时间

运动检测程序的视频时间记录问题

我用OpenCV实现了一个运动检测程序,输入是视频,输出包含运动发生时间的CSV文件。现在的问题是,我用datetime.now()记录运动发生的时间,但视频播放速度比实际快,导致无法准确记录视频实际播放时间,求解决办法。

原代码

def press_it():
    STime=0
    FTime=0
    moji = True
    first_frame = None
    status_list = [None,None]
    times = []
    startTime=datetime.now()
    print(startTime)
    #Dataframe to store the time values during which object detection and movement appears | "C:/Users/mojta/Desktop/videos/pred.mp4"
    df = pd.DataFrame(columns=['Start','End','Duration'])
    cam = cv2.VideoCapture(file)
    frames = cam.get(cv2.CAP_PROP_FRAME_COUNT)
    fps = cam.get(cv2.CAP_PROP_FPS)
    seconds = round(frames / fps)
    length = int(cam.get(cv2.CAP_PROP_FRAME_COUNT))
    print(length)
    y=int(values["-IN4-"])
    x=int(values["-IN3-"])
    h= int(values["-IN5-"])
    w= int(values["-IN6-"])

    #Iterate through frames and display the window
    while cam.isOpened():
        check, frame = cam.read()
        length-=1
        if moji==True:
            STime=datetime.now()
            moji=False

        frame = frame[y:y+h, x:x+w]
        #Status at beginning of the recording is zero as the object is not visisble
        status = 0

        #Converting each frame into gray scale image
        gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)

        #Convert grayscale image to GaussianBlur
        gray = cv2.GaussianBlur(gray, (21,21), 0)

        #This is used to store the first image/frame of the video
        if first_frame is None or length%500==0:
            first_frame = gray
            continue

        #Calculates the difference between the first frame and another frames
        delta_frame = cv2.absdiff(first_frame,gray)

        #Giving a threshold value, such that it will convert the difference value with less than 30 to black
        #If it is greater than 30, then it will convert those pixels to white
        _,thresh_delta = cv2.threshold(delta_frame, 30, 255, cv2.THRESH_BINARY)
        thresh_delta = cv2.dilate(thresh_delta, None, iterations=3)

        #Defining the contour area i.e., borders
        cnts,_ = cv2.findContours(thresh_delta.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        #Removes noises and shadows, i.e., it will keep only that part white, which has area greater than 10000 pixels
        Acuraccy = acuraccySlider
        for cont in cnts:
            if cv2.contourArea(cont) < Acuraccy:
                continue
            #Change in status when the object is being detected
            status = 1
            #creates a rectangular box around the object in the frame
            (x1, y1, w1, h1) = cv2.boundingRect(cont)
            cv2.rectangle(frame, (x1,y1), (x1+w1,y1+h1), (0,0,255), 3)

        #List of status for every frame
        status_list.append(status)
        status_list = status_list[-2:]

        #Record datetime in a list when change occurs
        if status_list[-1]==1 and status_list[-2]==0:
            times.append(datetime.now()-startTime)
            
        if status_list[-1]==0 and status_list[-2]==1:
            times.append(datetime.now()-startTime)

        #Opening all types of frames/images
        cv2.imshow("Grey Scale",gray)
        cv2.imshow("Delta", delta_frame)
        cv2.imshow("Threshold", thresh_delta)
        cv2.imshow("Colored frame",frame)

        last_frame_num = cam.get(cv2.CAP_PROP_FRAME_COUNT)
        #Generate a new frame after every 1 millisecond
        key = cv2.waitKey(1)
        #If entered 'q' on keyboard, breaks out of loop, and window gets destroyed
        
        print(length)
        if key == ord('q') or length<=10:
            if status==1:
                times.append(datetime.now()-startTime)
                FTime=datetime.now()
            break

    #Store time values in a Dataframe
    DURATION=FTime-STime
    FINAL = DURATION/seconds

    for i in range(0,len(times),2):
        if len(times)%2==1 and i==len(times)-1:
            break
        df = df.append({'Start':times[i],'End':times[i+1],'Duration':(times[i+1]-times[i])}, ignore_index=True)

    #Write the dataframe to a CSV file
    df.to_csv("Times.csv")

    cam.release()

    #Closes all the windows
    cv2.destroyAllWindows()
    window.Close()

解决方法

核心问题是用系统时间记录会受播放速度影响,改用视频帧序号 + 帧率计算视频内的实际时间,完全不受播放快慢干扰。

具体修改:

  1. 初始化帧计数器:在打开视频后添加帧计数变量,获取视频FPS
  2. 计算当前视频时间:每读取一帧就更新计数器,用帧计数器 / FPS得到当前视频的实际播放时间(单位:秒)
  3. 替换时间记录逻辑:把所有datetime.now()-startTime的地方换成计算出的视频时间

修改后的完整代码:

def press_it():
    STime=0
    FTime=0
    moji = True
    first_frame = None
    status_list = [None,None]
    times = []
    df = pd.DataFrame(columns=['Start','End','Duration'])
    cam = cv2.VideoCapture(file)
    frames = cam.get(cv2.CAP_PROP_FRAME_COUNT)
    fps = cam.get(cv2.CAP_PROP_FPS)
    seconds = round(frames / fps)
    length = int(cam.get(cv2.CAP_PROP_FRAME_COUNT))
    print(length)
    y=int(values["-IN4-"])
    x=int(values["-IN3-"])
    h= int(values["-IN5-"])
    w= int(values["-IN6-"])
    
    # 新增帧计数器
    frame_count = 0

    #Iterate through frames and display the window
    while cam.isOpened():
        check, frame = cam.read()
        if not check:
            break
        length-=1
        frame_count += 1
        # 计算当前视频的实际播放时间(秒)
        current_video_time = frame_count / fps
        
        if moji==True:
            STime=current_video_time
            moji=False

        frame = frame[y:y+h, x:x+w]
        status = 0

        gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
        gray = cv2.GaussianBlur(gray, (21,21), 0)

        if first_frame is None or length%500==0:
            first_frame = gray
            continue

        delta_frame = cv2.absdiff(first_frame,gray)
        _,thresh_delta = cv2.threshold(delta_frame, 30, 255, cv2.THRESH_BINARY)
        thresh_delta = cv2.dilate(thresh_delta, None, iterations=3)

        cnts,_ = cv2.findContours(thresh_delta.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        Acuraccy = acuraccySlider
        for cont in cnts:
            if cv2.contourArea(cont) < Acuraccy:
                continue
            status = 1
            (x1, y1, w1, h1) = cv2.boundingRect(cont)
            cv2.rectangle(frame, (x1,y1), (x1+w1,y1+h1), (0,0,255), 3)

        status_list.append(status)
        status_list = status_list[-2:]

        # 替换时间记录逻辑
        if status_list[-1]==1 and status_list[-2]==0:
            times.append(current_video_time)
            
        if status_list[-1]==0 and status_list[-2]==1:
            times.append(current_video_time)

        cv2.imshow("Grey Scale",gray)
        cv2.imshow("Delta", delta_frame)
        cv2.imshow("Threshold", thresh_delta)
        cv2.imshow("Colored frame",frame)

        key = cv2.waitKey(1)
        
        print(length)
        if key == ord('q') or length<=10:
            if status==1:
                times.append(current_video_time)
                FTime=current_video_time
            break

    # 更新时长计算逻辑
    DURATION=FTime-STime

    for i in range(0,len(times),2):
        if len(times)%2==1 and i==len(times)-1:
            break
        df = df.append({'Start':times[i],'End':times[i+1],'Duration':(times[i+1]-times[i])}, ignore_index=True)

    df.to_csv("Times.csv")

    cam.release()
    cv2.destroyAllWindows()
    window.Close()

额外说明:

  • 计算出的current_video_time是视频内的实际播放时间,和系统播放速度无关,哪怕逐帧播放,记录的时间依然是视频本身的进度
  • 如果需要更易读的时间格式,可以把秒数转成datetime.timedelta类型,比如timedelta(seconds=current_video_time),CSV中的时间会显示为00:00:XX格式

内容的提问来源于stack exchange,提问作者MojtabaMAleki02

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 18:20:32