視頻鏡頭分割

最近學習視頻和圖像的處理,剛好要先將視頻進行預(yù)處理判导。因為需要針對不同的鏡頭來做不一樣的處理咨察,所以在做進一步處理之前论熙,要將整段視頻進行分割處理。
先是在網(wǎng)上搜索了一下摄狱,找到一篇可以借鑒的文章 《視頻鏡頭分割與關(guān)鍵幀提取》脓诡。里面把整個算法思路都說的很清楚,雖然沒有具體的代碼實現(xiàn)媒役,這個不重要祝谚,思路比代碼更重要,按照對應(yīng)的思路酣衷,通過python寫出了具體的實現(xiàn)代碼交惯,可是最后在我處理的視頻上的效果不是很明顯,所以暫時將這個方案擱置穿仪。
后來在github找到了一個關(guān)于如何為視頻生成簡介的項目席爽,項目也是沒有太多的實現(xiàn),給了一篇國外論文的地址啊片∪花了三天時間總算把那篇難啃的英文論文看懂了,整理了一下里面的思路钠龙,然后將之前的思路結(jié)合起來炬藤,就寫出了我自己的實現(xiàn)代碼。
一碴里、邊緣檢測

根據(jù)幀圖像的灰度值直方圖差異進行邊緣檢測沈矿,差異值越大的幀可能就是鏡頭邊緣幀。這種方式可以避免在鏡頭移動或者圖像中出現(xiàn)動態(tài)移動的時候差異咬腋,提高邊緣檢測的準確性羹膳。其中要注意的地方
1、相鄰的兩個鏡頭根竿,中間的幀圖像個數(shù)應(yīng)該有一個閾值陵像,也就是說幀數(shù)相差太少不認可為新的一個鏡頭
2就珠、檢測出來的鏡頭邊緣幀,它與前一幀的差值應(yīng)該是此鏡頭中醒颖,所有幀差中最大的妻怎。其的值也應(yīng)該是當前鏡頭中所有幀差均值的一個倍數(shù)

二、具體算法
1泞歉、創(chuàng)建類逼侦,用來存儲每一幀的信息。

# 由于我處理的視頻一個就近2G腰耙,為了減少內(nèi)存的消耗榛丢,所以不會再內(nèi)存中存放幀的數(shù)據(jù)信息,只計算需要的數(shù)據(jù)后釋放掉
class frame_info:
    def __init__(self, index, diff):
        self.index = index  # 幀編號
        self.diff = diff  # 當前幀與前一幀的diff

2挺庞、創(chuàng)建函數(shù)晰赞,用來計算幀間差值。

           gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  
           n_pixel = frame.shape[0] * frame.shape[1] 
           hist = cv2.calcHist([gray], [0], None, [16], [0, 256])  
           hist = hist * (1.0 / n_pixel)
           diff = np.sum(np.abs(np.subtract(hist, pre_hist)))

這就是第一步遍歷視頻选侨,計算整個視頻中所有幀的幀差宾肺。存放到一個列表中,第一幀的幀差默認是0侵俗。
接下來就是對于邊緣幀的檢測

3锨用、找尋差值最大的幀,這里需要簡單說明一下算法思路

1隘谣、創(chuàng)建一個窗口增拥,定義窗口中幀的數(shù)量,每次對窗口中的幀進行判斷寻歧。然后取對應(yīng)數(shù)量的幀掌栅;
2、計算窗口中差值最大的幀码泛,定義為可疑的鏡頭邊緣幀M再進行下一步判斷猾封;
3、取得前一鏡頭邊緣幀P噪珊,判斷當前M與P中間的幀數(shù)量晌缘,是否超過設(shè)定的鏡頭最小幀數(shù)閾值,如果不超過痢站,則舍棄M磷箕,清空窗口數(shù)據(jù),進行下一個鏡頭判斷阵难;否則進行下一步判斷岳枷;
4、判斷M的差值是不是P到M的平均差值(不包括M的差值)的一個閾值倍數(shù)。

def second_find_diff_max(list_frames = [], start_no = 0):
    sus_max_frame = []  # 可疑的鏡頭幀空繁,以M為值
    window_frame = []

    length = len(list_frames)
    index_list = range(0, length)
    for index in index_list:
        frame_item = list_frames[index]
        window_frame.append(frame_item)

        if len(window_frame) < window_size:
            continue

        # 處理窗口幀的判斷
        max_diff_frame = getMaxFrame(window_frame)
        max_diff_index = max_diff_frame.index

        if len(sus_max_frame) == 0:
            sus_max_frame.append(max_diff_frame)
            continue
        last_max_frame = sus_max_frame[-1]

        '''
            判斷是否超過鏡頭跨度最小值
            1殿衰、低于,則移除窗口中最大幀之前的所有幀(包括最大幀)盛泡,然后重新移動窗口
            2闷祥、則進入下一步判斷
        '''
        if (max_diff_index - last_max_frame.index) < m_MinLengthOfShot:
            start_index = window_frame[0].index
            if last_max_frame.diff < max_diff_frame.diff:
                #  最后一條可疑frame失效
                sus_max_frame.pop(-1)
                sus_max_frame.append(max_diff_frame)
                pop_count = max_diff_index - start_index + 1
            else:
                #  舍棄當前的可疑frame,整個窗口清除
                pop_count = window_size

            count = 0
            while True:
                window_frame.pop(0)
                count += 1
                if count >= pop_count:
                    break
            continue

        '''
            鏡頭差超過最小鏡頭值后的下一步判斷饭于,判斷是否為可疑幀
            當前最大幀距離上一個可疑幀的平均差值是否差距很大
        '''
        sum_start_index = last_max_frame.index + 1 - start_no
        sum_end_index = max_diff_index - 1 - start_no
        id_no = sum_start_index
        # print("{0}, {1}, {2}".format(sum_start_index, sum_end_index, id_no))
        sum_diff = 0
        while True:

            sum_frame_item = list_frames[id_no]
            sum_diff += sum_frame_item.diff
            id_no += 1
            if id_no > sum_end_index:
                break

        average_diff = sum_diff / (sum_end_index - sum_start_index + 1)
        if max_diff_frame.diff >= (m_suddenJudge * average_diff):
            sus_max_frame.append(max_diff_frame)

        window_frame = []
        continue

    sus_last_frame = sus_max_frame[-1]
    last_frame = list_frames[-1]
    if sus_last_frame.index < last_frame.index:
        sus_max_frame.append(last_frame)

    return sus_max_frame

4蜀踏、在上一步的處理后维蒙,其實效果已經(jīng)可以實現(xiàn)部分了。但是在實際的測試中,發(fā)現(xiàn)效果還是有一定的問題颓芭,所以做了一個簡單的優(yōu)化步驟

具體的優(yōu)化處理思路就是彻犁,處理在上面中在連續(xù)幀差值都比較波動大的時候,很容易出現(xiàn)鏡頭幀獲取錯誤的問題斑响。還有就是陡增菱属、陡降的時候,鏡頭邊緣幀的判斷失誤問題舰罚。
整體的思路就是:
1纽门、如果當前可疑幀是陡增情況,即其前面的多個幀的差值都很低营罢,突然其卻很高赏陵,形成了一個近乎近90度的陡增效果,則認為其為鏡頭邊緣幀
2饲漾、陡降的效果蝙搔,類似于陡增處理
3、當前找到的可疑邊緣幀考传,其幀差值應(yīng)該是其附近(前后)一定幀數(shù)范圍內(nèi)的最大值吃型;

def third_optimize_frame(tag_frames, all_frames, start_no):
    '''
        進一步優(yōu)化
        對于每一個分割鏡頭幀,其前后的幀的平均值都遠遠低于其
    '''
    new_tag_frames = []
    for tag_frame in tag_frames:

        tag_index = tag_frame.index

        if tag_frame.diff < m_diff_threshold:
            continue

        #  向前取m_MinLengthOfShot個幀
        pre_start_index = tag_index - m_offset_frame_count - m_offset
        pre_start_no = pre_start_index - start_no
        if pre_start_no < 0:
            #  如果往前找時已經(jīng)到頭了僚楞,則認為此鏡頭不可取勤晚,將鏡頭交給最起始的幀
            new_tag_frames.append(all_frames[0])
            continue
        pre_end_no = tag_index - 1 - start_no - m_offset

        pre_sum_diff = 0
        emulator_no = pre_start_no
        while True:
            pre_frame_info = all_frames[emulator_no]
            pre_sum_diff += pre_frame_info.diff
            emulator_no += 1
            if tag_frame.index == 42230:
                print("向前:{0}, {1}".format(pre_frame_info.index, pre_frame_info.diff))
            if emulator_no > pre_end_no:
                break

        #  向后取m_MinLengthOfShot個幀
        back_end_index = tag_index + m_offset_frame_count + m_offset
        back_end_no = back_end_index - start_no
        if back_end_no >= len(all_frames):
            #  如果往后找時已經(jīng)到頭了,則認為此鏡頭不可取泉褐,將鏡頭交給結(jié)束的幀
            new_tag_frames.append(all_frames[-1])
            continue
        back_start_no = tag_index + 1 - start_no + m_offset

        back_sum_diff = 0
        emulator_no = back_start_no
        while True:
            back_frame_info = all_frames[emulator_no]
            back_sum_diff += back_frame_info.diff
            emulator_no += 1
            if emulator_no > back_end_no:
                break

        is_steep = False
        # 判斷是不是陡增/或者陡降
        pre_average_diff = pre_sum_diff / m_offset_frame_count
        print("前平均 {0}, {1}, {2}".format(tag_frame.index, tag_frame.diff, pre_average_diff))
        if tag_frame.diff > (m_optimize_steep * pre_average_diff):
            is_steep = True

        back_average_diff = back_sum_diff / m_offset_frame_count
        print("后平均 {0}, {1}, {2}".format(tag_frame.index, tag_frame.diff, back_average_diff))
        if tag_frame.diff > (m_optimize_steep * back_average_diff):
            is_steep = True

        # 計算平均值运翼,如果大于一定的閾值倍數(shù),則認可兴枯,不然舍棄
        sum_diff = pre_sum_diff + back_sum_diff
        average_diff = sum_diff / (m_offset_frame_count * 2)
        print("{0}, {1}, {2}".format(tag_frame.index, tag_frame.diff, average_diff))
        if tag_frame.diff > (m_optimize * average_diff) or is_steep:
            new_tag_frames.append(tag_frame)

    return new_tag_frames

這樣就得到了所有鏡頭幀的編號血淌,然后對應(yīng)視頻找鏡頭幀編號就行了。我用了視頻進行測試,得到的效果還是很滿意的悠夯。


測試結(jié)果圖片

其中33-0表示:截下來的視頻鏡頭總數(shù)量——有問題的鏡頭數(shù)量
這樣整體的結(jié)果就是:230——10癌淮,而且其中的錯誤鏡頭還有是因為視頻花屏導(dǎo)致的。所以最后的成功率基本在90%以上沦补,基本滿足了需求乳蓄。

如果有疑問或者有更好的建議,我們可以一起探討夕膀,共同進步虚倒!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市产舞,隨后出現(xiàn)的幾起案子魂奥,更是在濱河造成了極大的恐慌,老刑警劉巖易猫,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耻煤,死亡現(xiàn)場離奇詭異,居然都是意外死亡准颓,警方通過查閱死者的電腦和手機哈蝇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來攘已,“玉大人炮赦,你說我怎么就攤上這事⊙” “怎么了吠勘?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長彤灶。 經(jīng)常有香客問我看幼,道長,這世上最難降的妖魔是什么幌陕? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任诵姜,我火速辦了婚禮,結(jié)果婚禮上搏熄,老公的妹妹穿的比我還像新娘棚唆。我一直安慰自己,他們只是感情好心例,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布宵凌。 她就那樣靜靜地躺著,像睡著了一般止后。 火紅的嫁衣襯著肌膚如雪瞎惫。 梳的紋絲不亂的頭發(fā)上溜腐,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音瓜喇,去河邊找鬼挺益。 笑死,一個胖子當著我的面吹牛乘寒,可吹牛的內(nèi)容都是我干的望众。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼伞辛,長吁一口氣:“原來是場噩夢啊……” “哼烂翰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚤氏,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤甘耿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瞧捌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棵里,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡润文,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年姐呐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片典蝌。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡曙砂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骏掀,到底是詐尸還是另有隱情鸠澈,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布截驮,位于F島的核電站笑陈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏葵袭。R本人自食惡果不足惜涵妥,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坡锡。 院中可真熱鬧蓬网,春花似錦、人聲如沸鹉勒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禽额。三九已至锯厢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背实辑。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工臣疑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徙菠。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓讯沈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婿奔。 傳聞我的和親對象是個殘疾皇子缺狠,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 個人為了看的方便沒有廣告,轉(zhuǎn)載自泡泡機器人:https://www.sohu.com/a/161346283_71...
    Maxsium閱讀 38,937評論 0 8
  • 五月,天逐漸熱了冰木,我的心卻慌了穷劈。六月的高考在不斷靠近,家里的氣氛也逐漸緊張踊沸,就在這時身體一向健壯的姥爺卻病了歇终。家里...
    欣_宇閱讀 279評論 0 0
  • 如果一件事讓我們苦苦糾結(jié),那么還是放下心中那份計算逼龟,跟隨本心去做就好了评凝。 心之所向的事情往往都是自然而然,不需要左...
    Ada岳慧閱讀 289評論 0 0
  • 從小我就喜歡和比我大的人玩,因為可以得到義務(wù)性的照顧和免費的人生經(jīng)驗匀钧。而最近我認識了一批比我小的朋友翎碑,其中有一個可...
    muuul閱讀 625評論 0 0
  • 前言 為什么會有視覺筆記吊圾、手帳达椰、插畫這些混淆點在大家的腦海中呢? 剛接觸視覺化表達的小伙伴一定都有過這樣的疑惑项乒,視...
    明霞菇?jīng)?/span>閱讀 2,124評論 0 2