用 OpenCV 去除圖片中的水印,騷操作!


參考連接:https://stackoverflow.com/questions/32125281/removing-watermark-out-of-an-image-using-opencv

好久不見,大家好啊,最近太忙了,搞得好久沒更原創文了(說到底還是懶,),

這兩天在 Stackoverflow 上面看到了一個有趣的案例,是關於OpenCV 的一個討論,討論的主題就是如何用 OpenCV 來去除下面圖片中的水印,原圖如下;

shuiyin.jpg

題主想把紙張中的 黑色圓環去掉只留下背景,因此一些感興趣的 CV 愛好者在下面寫上自己的想法、並貼上自己的解決代碼

看到關於這個主題的答案后,只能感嘆真正的大佬,都是從實踐場景出發來解決問題,

因為篇幅有限,在文章中只貼上得票最高的兩個問答思路及代碼, 讓我們感受下他們思路的巧妙之處!

作者:Joel G

這老哥的思路,總體為五部分

  • 1,首先將圖像轉化為灰度圖記為 A;

  • 2,利用霍夫圓在 A 中檢測最大的橢圓,然后在新的圖像中創建相同半徑的圓得到 B;

  • 3,對灰度圖和繪制圓的圖像,應用OpenCV 的 bitwise_and 與運算,在原灰度圖像 A 中提取只包含橢圓圖像區域記為 C;

  • 4,對圖像 C 設置合適的閾值進行文字提取最終得到 D;

  • 5, 對 圖像 A 和 D 做bitwise_or 操作,即能夠得到最終圖像 E;

以下是在自己機子上跑出來的結果,從左到右依次對應上面的 A,C,D,E;效果如下

threshold1.jpg

這個方法整體大概思想,先提取圖像中圓環部分區域,對圓環內的文字做閾值分割進行提取,最后將提取到的圖像區域在初始圖像中進行替換

這里答主主要用到了三種重要算法:圖像位運算(和、或)閾值分割霍夫圓檢測

下面就是這個思路的代碼部分,原答主用的是 C++ ,因為我做的是 Python 教程,就用 Python 轉換了一下

import cv2
import numpy as np

if __name__ =='__main__':
    img_path = "F:/Data/Ceshi1/shuiyin.jpg"

    img1 = cv2.imread(img_path)
    cv2.namedWindow('img1',cv2.WINDOW_FREERATIO)
    cv2.imshow('img1',img1)

    # 轉化為 灰度圖
    gray = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
    # 創建一個白畫布
    ellipse_img = np.full((img1.shape[0],img1.shape[1],3),0,dtype = np.uint8)
    print(ellipse_img.shape,ellipse_img[0][0])
    gray = cv2.GaussianBlur(gray,(5,5),0) # 高斯處理
    # 應用霍夫圓檢測,檢測出所有圓
    circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,gray.shape[0]/8,100,100,100,0)


    # 找到最大的圓
    measure = 0.0
    x = 0.0
    y = 0.0
    for circle in (circles[0]):
        if circle[2] > measure:
            measure = circle[2]
            x = circle[0]
            y = circle[1]

    # 繪制圓
    cv2.circle(img1,(x,y),3,(0,255,0),-1,8,0)
    cv2.circle(img1,(x,y),int(measure),(0,255,0),2,8,0)
    # 繪制相同大小的圓
    ellipse_img =  cv2.ellipse(ellipse_img,(x,y),(int(measure),int(measure)),0,0,360,(255,255,255),-1,8)
    print(f'center x is {x} ,y is {y}, radius is {measure}')
    ellipse_img = cv2.cvtColor(ellipse_img,cv2.COLOR_BGR2GRAY)

    result = cv2.bitwise_and(gray,ellipse_img)


    cv2.namedWindow('bitwise and',cv2.WINDOW_FREERATIO)
    cv2.imshow('bitwise and',result)

    # 估計圓圖像像素強度
    x = result[int(x+30)][int(y)]
    print(f'intensity is  {x}')


    # 閾值分割
    _,ellipse_img = cv2.threshold(result,int(x) - 10,250,cv2.THRESH_BINARY)
    # print('ellipse_img shape is {}'.format(ellipse_img.shape))
    cv2.namedWindow('threshold',cv2.WINDOW_FREERATIO)
    cv2.imshow('threshold',ellipse_img)

    # 使用 bitwise_or 方法
    print('shape ------------\n')
    print(ellipse_img.shape,gray.shape)
    res = cv2.bitwise_or(gray,ellipse_img)

    cv2.namedWindow('bitwise_or',cv2.WINDOW_FREERATIO)
    cv2.imshow('bitwise_or',res)

    cv2.waitKey(0)

最終結果預覽比對

reslut_1.jpg

上面是第一種實現方法,這種方法思路主要用到閾值分割,從最終結果來看確實去掉了水印,但還是有一定的瑕疵:

  • 比如圓內文字背景與圓外背景是不一樣的,存在很大色差,並且圓內的文字提取結果來看是不完整的;
  • 此方法不具有普遍性,因為這類方法只能針對於圓形水印,假設水印是不規則多邊形此方法可能就會失效

下面介紹第二種思路,與第一種有相似的地方,也用到了閾值分割、圖像像素位運算 相關算法,但同卻又有自己的獨特地方,從客觀角度分析來看,這種方法的最終結果會更好一點

作者:dhanushka

思路主要分為四部分

  • 1,源圖像記為 A,用形態學濾波器刪除圖像中文字區域,得到的圖像記為 B;

Snipaste_2020-12-11_21-17-22.jpg

  • 2,獲取A,B 圖像的之差,用 A-B ,得到區別后再用閾值分割進行處理,得到 C;

Snipaste_2020-12-11_21-17-53.jpg

  • 3,閾值分割背景圖像,提取水印覆蓋黑色部分記為 D,

Snipaste_2020-12-11_21-18-00.jpg

  • 4,從 A 中提取在區域 D 中的像素,再用閾值分割方法分割像素,最終將提取到的像素貼到 B 中,得到最終去除水印的圖像

Snipaste_2020-12-11_21-18-11.jpg

代碼貼在下方

import cv2
import numpy as np

if __name__ =='__main__':
    img_path = "F:/Data/Ceshi1/shuiyin.jpg"
    im = cv2.imread(img_path)

    gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

    background = gray.copy()
    for i in range(1,5):
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(2*i+1,2*i+1))
        # print('kernel size is ',kernel)
        background = cv2.morphologyEx(background,cv2.MORPH_CLOSE,kernel)
        background = cv2.morphologyEx(background,cv2.MORPH_CLOSE,kernel)

    diff = background - gray # 計算差距

    cv2.namedWindow('diff',cv2.WINDOW_FREERATIO) # 獲取圖像中前景背景之差
    cv2.imshow('diff',background)
    # 閾值分割獲取黑色字體
    _,bw = cv2.threshold(diff,0,255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
    # 閾值分割獲取黑色區域
    cv2.namedWindow('bw_before', cv2.WINDOW_FREERATIO)
    cv2.imshow('bw_before', bw)


    _,dark = cv2.threshold(background,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

    darkpix = cv2.countNonZero(dark)# 獲取 dark非0d圖像像素個數
    darkpix = [0]*darkpix
    index = 0
    cv2.namedWindow('gray', cv2.WINDOW_FREERATIO)
    cv2.imshow('gray', gray)



    for r in range(dark.shape[0]):
        for c in range(dark.shape[1]):
            if(dark[r][c]):
                darkpix[index]  = gray[r][c]
                index = index +1

    # 閾值分割 dark 區域 因此我們在里面得到更深的像素
    darkpix = np.array(darkpix)
    _,darkpix = cv2.threshold(darkpix,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)

    cv2.namedWindow('darkpix', cv2.WINDOW_FREERATIO)
    cv2.imshow('darkpix', darkpix)

    # 把 取到的像素粘貼到 其渠道的 darker pixels

    cv2.namedWindow('dark',cv2.WINDOW_FREERATIO)
    cv2.imshow('dark',dark)

    index = 0
    for r in range(dark.shape[0]):
        for c in range(dark.shape[1]):
            if (dark[r][c]):
                bw[r][c] =  darkpix[index]
                index = index +1

    cv2.namedWindow('bw',cv2.WINDOW_FREERATIO)
    cv2.imshow('bw',bw)
    cv2.waitKey(0)

效果預覽對比

Snipaste_2020-12-12_17-04-44.jpg

相對第一種方法,第二種方法實用性更強一點,無論圖像前景水印為什么形狀的,這種方法都可適用(水印區域與其他背景像素強度差別大,且水印區域是連接在一起的),

如果考慮到商用途徑,只用 OpenCV 來解決復雜場景的圖片水印問題,是不現實的,還需人工的干涉;但不現實並不代表它沒有用,對於前后像素值較大、簡單場景的水印,OpenCV 是完全可行的,若是再加上一個批量操作,變得更可了,大大解放我們的雙手!

並且這兩種思路中用到的的一些方法,是值得我們借鑒的,比如 圖像像素或與和操作、形態學過濾、霍夫圓檢測等技術,可借助於這些方法應用到其它場景,例如提取圖像中圓形區域、行人路上斑馬線檢測、去除不規則圖像連接區域等。

好了,以上就是本篇文章的全部內容了,如果覺得不錯,請不要吝嗇你的雙手,點贊、轉發、留言,感謝三連!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM