图形图像算法中必须要了解的设计模式(3)

  • 2019 年 11 月 13 日
  • 筆記

随着信息的多元化,信息的概念不仅仅指的是文字,它还包含图片、声音、视频等其它丰富的信息。文字信息越来越多地被图片、声音、视频信息所替代,而视频又是由一针一针的图像组成的,因此图形图像的处理变得越来越热门和重要,众多的专家、学者、工程师投入到这个领域。

作为一个图像算法的研究者,写出一手高级算法当然是令人兴奋的一件事!但你是否有时会有这种感觉:

  1. 写的算法很难通用于所有的数据类型!每来一个新类型的数据,又得改一下算法,或新加一个方法来支持这种类型。
  2. 有时候多个算法需要灵活组合,甚至每个算法的顺序不一样都会产生不一样的效果;每一种组合都要为其构建一个新算法,即累又麻烦。
  3. 算法越来越多,自建的算法库也越来越庞大而难于管理;

这个时候,我们可以使用一些设计模式来设计你的代码,让你的算法具有更好的通用性和拓展性!前面我们已经写了《图形图像算法中必须要了解的设计模式(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)

算法核模板

垂直微分算子

定义

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)

算法核模板

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微分计算后:

这样设计之后,我们的代码是不是非常简洁明了,而且容易拓展。是不是非常简单,因为模板方法模式只是用了面向对象的继承机制。而这种继承方式,你在自己写的代码中可能很多地方已经有意无意就这么用了。