1. 程式人生 > >聊聊那些專為演算法設計的模式——模板方法模式

聊聊那些專為演算法設計的模式——模板方法模式

AI越來越火熱,人工智慧已然成風!而人工智慧最重要是各種演算法,因此機器學習越來越受到追捧,演算法越來越被重視。

作為一個演算法的研究者,寫出一手高階演算法當然是令人興奮的一件事!但你是否有時會有這種感覺:
1. 寫的演算法很難通用於所有的資料型別!每來一個新型別的資料,又得改一下演算法,或新加一個方法來支援這種型別。
2. 有時候多個演算法需要靈活組合,甚至每個演算法的順序不一樣都會產生不一樣的效果;每一種組合都要為其構建一個新演算法,即累又麻煩。
3. 演算法越來越多,自建的演算法庫也越來越龐大而難於管理;

這個時候,讓你的演算法具有更好通用性、拓展性就顯得極為重要!因此,你必須要掌握幾個重要的設計模式來優化你的程式碼,解決這些問題。今天就來聊聊那些專為演算法設計的模式:策略模式、模板方法模式、訪問模式。

模板方法模式

Define the skeleton of an algorithm in an operation, deferring some steps to client subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

定義一個操作中的演算法的框(骨)架,而將演算法中用到的某些具體的步驟放到子類中去實現,使得子類可以在不改變演算法結構的情況下重新定義該演算法的某些特定步驟。這個定義演算法骨架的方法就叫模板方法

對一些複雜的演算法進行分割,將其演算法中固定不變的部分設計為模板方法和父類具體方法,而一些可以改變的細節由其子類來實現。即一次性實現一個演算法的不變部分,並將可變的行為留給子類來實現。

應用案例

模板方法模式非常簡單,以至於我都不覺得它是一個模式。因為只要是在使用面向物件的語言進行開發,你就有意無意之中已經在使用它了,舉一個例子。

在圖形影象的處理中,對影象畫素進行微分求導,進行影象的銳化處理,是一個非常基礎而又重要的演算法。在對影象的一階微分求導演算法中,有兩個非常重要的演算法:水平微分運算元垂直微分運算元。另一個非常著名的演算法Sobel微分運算元,也是基於這兩個演算法來實現的。

水平微分運算元

定義

Gx=f(x-1,y-1) + 2f(x-1,y) + f(x-1,y+1) - f(x+1,y-1) - 2f(x+1,y) - f(x+1, y+1)

演算法核模板
Alt text

垂直微分運算元

定義

Gy=f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1) - f(x-1,y+1) - 2f(x,y+1) - f(x+1,y+1)

演算法核模板
Alt text

Sobel微分運算元

定義

Gx=f(x-1,y-1) + 2f(x-1,y) + f(x-1,y+1) - f(x+1,y-1) - 2f(x+1,y) - f(x+1, y+1)
Gy=f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1) - f(x-1,y+1) - 2f(x,y+1) - f(x+1,y+1)
G=sqrt(Gx^2 + Gy^2)

也就是對水平微分和垂直微分的兩個計算結果,再進行算術平方計算。

自己實現這個演算法

雖然像OpenCv等這些成熟的圖形影象演算法庫都提供了這一基礎的演算法,但作為一個圖形影象演算法的研究者,你有沒有想過自己去實現一下這個簡單的演算法!我是有的,你呢?

水平微分運算元和垂直微分運算元這兩個演算法非常的相似,不同之處在於:一個是在水平方向上處理,一個是在垂直方向上處理。

這兩個演算法既然如此的相似,那肯定會有一些共同的部分,如畫素的遍歷:也有不同的部分,如演算法的核模板不同。這個時候,我們就可以考慮使用模板方法的設計了。來看一下我們實現(下面這段程式碼應用到的拓展庫:opencv-3.4.1,numpy-1.14.5):

演算法實現:

from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod來定義抽象類和抽象方法
import numpy as np
# 引入numpy模組進行矩陣的計算

class DifferentialDerivative(metaclass=ABCMeta):
    """微分求導演算法"""

    def imgProcessing(self, img, width, height):
        """模板方法,進行影象處理"""
        # 這裡特別需要注意:OpenCv for Python中,(x, y)座標點的畫素用img[y, x]表示
        newImg = np.zeros([height, width], dtype=np.uint8)
        for y in range(0, height):
            for x in range(0, width):
                # 因為是採用(3*3)的核進行處理,所以最邊上一圈的畫素無法處理,需保留原值
                if (y != 0 and y != height-1 and x != 0 and x != width-1):
                    value = self.derivation(img, x, y)
                    # 小於0的值置為0,大於255的值置為255
                    value = 0 if value < 0 else (255 if value > 255 else value)
                    newImg[y, x] = value
                else:
                    newImg[y, x] = img[y, x]
        return newImg

    @abstractmethod
    def derivation(self, img, x, y):
        """具體的步驟由子類實現"""
        pass

class DifferentialDerivativeX(DifferentialDerivative):
    """水平微分求導演算法"""

    def derivation(self, img, x, y):
        """Gx=f(x-1,y-1) + 2f(x-1,y) + f(x-1,y+1) - f(x+1,y-1) - 2f(x+1,y) - f(x+1, y+1)"""
        pix = img[y-1, x-1] + 2 * img[y, x-1] + img[y+1, x-1] - img[y-1, x+1] - 2 *img[y, x+1] - img[y+1, x+1]
        return pix


class DifferentialDerivativeY(DifferentialDerivative):
    """垂直微分求導演算法"""

    def derivation(self, img, x, y):
        """Gy=f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1) - f(x-1,y+1) - 2f(x,y+1) - f(x+1,y+1)"""
        pix = img[y-1, x-1] + 2*img[y-1, x] + img[y-1, x+1] - img[y+1, x-1] - 2*img[y+1, x] - img[y+1, x+1]
        return pix

測試程式碼:

def differentialDerivative():
    img = cv2.imread("E:\\TestImages\\person.jpg")

    # 轉換成單通道的灰度圖
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 均值濾波
    # img = cv2.blur(img, (3, 3))

    # 獲取圖片的寬和高
    width = img.shape[1]
    height = img.shape[0]
    # 進行微分求導
    derivativeX = DifferentialDerivativeX()
    imgX = derivativeX.imgProcessing(img, width, height)
    derivativeY = DifferentialDerivativeY()
    imgY = derivativeY.imgProcessing(img, width, height)
    # 實現Sobel微分運算元
    imgScobel = cv2.addWeighted(imgX, 0.5, imgY, 0.5, 0)

    cv2.imshow("First order differential X", imgX)
    cv2.imshow("First order differential Y", imgY)
    cv2.imshow("First order differential Scobel", imgScobel)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

實現的效果與OpenCv的效果一模一樣,我們一起來看一下。

原圖:
這裡寫圖片描述

水平微分求導後:
這裡寫圖片描述

垂直微分求導後:
這裡寫圖片描述

Sobel微分計算後:
這裡寫圖片描述

更多更有趣的文章

想獲得更多更有趣的設計模式嗎?一起來閱讀以下系列文章吧!

程式原始碼

引導篇

基礎篇

進階篇

經驗篇