结合我们822实验室开源的图像处理平台(http://822lab.top)介绍用责任链模式实现图像处理方法的选择(python),供后续学弟学妹参考,整个平台的从零搭建记录在这里,后端仓库在这里,前端仓库在这里,欢迎大家为平台做贡献。
需求:
图像处理方法可以分为几个大类,比如图像平滑、轮廓提取、角点检测、形态学处理等,每一个大类下又有很多小类,比如图像平滑有高斯平滑、中值平滑和均值平滑等,我希望用户选择某个小类方法,然后得到相应的图像处理结果。因此在设计的时候给每个方法一个编号code
,字符串类型,是一个三位数,第一位代表大类,后两位代表小类,客户请求时带着图片和code
进行请求,服务器返回处理后的图片。
设计思路:
大类与小类的code记在一个枚举类中,如下:
# coding=utf-8
from enum import Enum
class ProcCodeEnum(Enum):
# 图像平滑
GAUSSIAN_BLUR = '101'
MEAN_BLUR = '102'
MEDIA_BLUR = '103'
B_F_BLUR = '104'
JOINT_B_F_BLUR = '105'
# 阈值分割
AUTO_THRESHOLD = '201'
MANUAL_THRESHOLD = '202'
ADAPTIVE_THRESHOLD = '203'
# 轮廓提取
ACCURATE_CONTOUR = '301'
FITTING_CONTOUR = '302'
# 角点提取
HARRIS_CORNER = '401'
SUBPIX_CORNER = '402'
R_J_CORNER = '403'
# 形态学处理
ERODE = '501'
DILATE = '502'
MORPH_OPERATIONS_CAL = '503'
在进行设计的时候,最容易产生的思路是:根据传入的code
,用if else判断是哪个算法,然后进行处理。因为我不喜欢代码中有很长的if else语句,尤其这么多的算法,可能很长一大段if else,这是一件恐怖的事情,因此自然想到了责任链模式,用责任链有两个好处:
- 免掉很多if else语句。
- 扩展时候容易,只需要以同样模式新增代码就好,不需要改动已有逻辑。
最初设计责任链时候,我设想的是所有小算法都在一个链下,对应的画面是:图像处理算法由一个manager管,这个manager负责所有算法,code
来了只要交给他就能出结果。但是很快就面临一个问题:
- 随着图像处理算法的增加,manager的负担太大,虽然他不需要进行具体处理,但是他没接到一个任务需要挨家挨户敲门去问手下的工人能不能进行处理,显然不是很好。
因此把责任链粒度缩小到图像处理算法的每一个大类都使用一个责任链,对应的画面是:有n个manager负责不同类的图像处理算法,是哪个类的就交给哪个manager,每个manager管的工人都不多,因此会合理一些。(注意我说的是合理,不是快,对于工程项目来说,拉慢的一点点效率,对整体影响不大,提高的一点点效率,对整体影响也不大,因此合理是最重要的)。
详细设计:
责任链模式的关键,在java里是每个类要实现的接口,在python是每个类要继承的父类,里面包含to_next方法和handle方法,to_next是链条里的下一个人,handle是具体的处理方法,从第一个人起,每个人会先尝试handle,如果handle不了,再调用to_next的handle,也就是让下一个人处理,下一个人处理不了再交给下下个人,直到链条最后。
handler.py
class Handler(object):
def __init__(self, _to_next=None):
self._to_next = _to_next
def to_next(self, _to_next):
self._to_next = _to_next
def handle(self, code, params, image):
pass
每个的小算法类都继承Handler,实现handle方法。
如高斯平滑gaussian_blur.py
import cv2
from img_algorithms.algorithms_base import Handler
from img_algorithms.algorithms_base import get_border_type
from myenums.proc_code_enum import ProcCodeEnum
def gaussian_blur(image, k_size_w, k_size_h, sigma_x, sigma_y, border_type):
return cv2.GaussianBlur(image,
ksize=(k_size_w, k_size_h),
sigmaX=sigma_x,
sigmaY=sigma_y,
borderType=get_border_type(border_type))
# 继承Handle,如果自己能处理,则处理,处理不了,则交给下一个(调用to_next的handle)
class GaussianBlurHandler(Handler):
def handle(self, code, params, image):
if code == ProcCodeEnum.GAUSSIAN_BLUR:
return gaussian_blur(image,
int(params['kSizeW']),
int(params['kSizeH']),
float(params['sigmaX']),
float(params['sigmaY']),
int(params['borderType']))
else:
return self._to_next.handle(code, params, image)
链条的最后一个将不会调用to_next,而是抛出异常(这里只是用异常机制控制了业务逻辑)。
我们设计所有的manager最后一个链条元素都是no_process.py
# coding=utf-8
from img_algorithms.algorithms_base import Handler
class NoProcHandler(Handler):
def handle(self, code, params, image):
raise Exception('哎呀~{}号图像处理方法尚未完成,请静候佳音'.format(code))
最后是链条的组织者,也就是每类算法的manager,还是以图像平滑为例:
from img_algorithms.algorithms_base import NoProcHandler
from img_algorithms.algorithms_smooth import BFBlurHandler
from img_algorithms.algorithms_smooth import GaussianBlurHandler
from img_algorithms.algorithms_smooth import JointBFBlurHandler
from img_algorithms.algorithms_smooth import MeanBlurHandler
from img_algorithms.algorithms_smooth import MediaBlurHandler
def process(code, params, image):
# 实例化每个小算法
gaussian_blur = GaussianBlurHandler()
mean_blur = MeanBlurHandler()
media_blur = MediaBlurHandler()
b_f_blur = BFBlurHandler()
joint_b_f_blur = JointBFBlurHandler()
no_proc_handler = NoProcHandler()
# 拼成链条
gaussian_blur.to_next(mean_blur)
mean_blur.to_next(media_blur)
media_blur.to_next(b_f_blur)
b_f_blur.to_next(joint_b_f_blur)
joint_b_f_blur.to_next(no_proc_handler)
return gaussian_blur.handle(code, params, image)
最外层调用的时候只需要:
...
if int(o_code) < 200:
processed_img_arr = smooth_manager.process(o_code, o_params, img_arr)
...
PR新增算法
平台仓库在这里,欢迎大家进行完善。
新增图像处理大类
- 在proc_code_enum.py中添加大类以及code,注释与命名风格请一定统一。
- 在img_algorithms中新建大类的package,并在img_algorithms的
__init__.py
文件中import manager。 - 在新建的package中写用责任链模式写新的小类算法。
- 在img_lab.py的判断code中,调用新类manager的process方法,注意,这里读入的图都是彩色BGR图,如需要灰度图,请在算法中自行转换。
新增图像处理小类
- 在proc_code_enum.py中添加小类code,注释与命名风格请一定统一。
- 从img_algorithms中找到大类,在里面新建小类模块,并在大类的
__init__.py
中import 方法。
数据库添加
由于数据库添加接口还没做,所以需要联系我(QQ或微信644306737,备注server-gitPR+姓名),我来进行数据库更新。组织成json发给我即可,形如:
{
"name" : "形态学运算",
"code" : "503",
"type" : [
{
"name" : "形态学处理"
}
],
"params" : [
{
"type" : "input",
"name" : "结构元大小",
"value" : [],
"limit" : "odd",
"pName" : "kSize"
},
{
"type" : "select",
"name" : "结构元形状",
"value" : [
"矩形",
"椭圆形",
"十字交叉形"
],
"limit" : "",
"pName" : "shape"
},
{
"type" : "input",
"name" : "迭代次数",
"value" : [],
"limit" : "int >0",
"pName" : "iterations"
},
{
"type" : "select",
"name" : "运算",
"value" : [
"开运算",
"闭运算",
"形态梯度",
"顶帽运算",
"底帽运算"
],
"valueDesc" : [
"先腐蚀再膨胀,可以消除白色小物体",
"先膨胀再腐蚀,可以消除黑色小物体",
"膨胀图与腐蚀图之差,对二值图可以出现中空效果,类似华文彩云字体",
"原图像与开运算做差,可以用来分离比邻近点亮的斑块",
"原图像和闭运算做差,可以用来分离比邻近点暗的斑块"
],
"limit" : "",
"pName" : "op"
},
{
"type" : "select",
"name" : "边界扩充方式",
"value" : [
"边界复制",
"常数扩充",
"反射扩充",
"边界为中心反射扩充",
"平铺扩充"
],
"valueDesc" : [
"边界复制:BORDER_REPLICATE,复制最外围像素实现扩充",
"常数扩充:BORDER_CONSTANT,用指定常数扩充边界,这里全部用0扩充",
"反射扩充:BORDER_REFLECT,对图片进行镜像扩充",
"边界为中心反射扩充:BORDER_REFLECT_101,以四个边界分别为对称轴镜像扩充,比反括扩充少了一圈边界值的复制",
"平铺扩充:BORDER_WRAP,同桌面平铺效果"
],
"limit" : "",
"pName" : "borderType"
}
]
}