我在我的一个旧项目中得到以下代码:
-- |ImageOperation is a name for unary operators that mutate images inplace.
newtype ImageOperation c d = ImgOp (Image c d-> IO ())
-- |Compose two image operations
(#>) :: ImageOperation c d-> ImageOperation c d -> ImageOperation c d
(#>) (ImgOp a) (ImgOp b) = ImgOp (\img -> (a img >> b img))
-- |An unit operation for compose
nonOp = ImgOp (\i -> return ())
-- |Apply image operation to a Copy of an image
img <# op = unsafeOperate op img
-- | Apply the operation on a clone of an image
operate (ImgOp op) img = withClone img $ \clone ->
op clone >> return clone
unsafeOperate op img = unsafePerformIO $ operate op img
它的主要目的是允许组合运行到位的opencv运算符并接受相同格式和维度的图像。这是一个重要的优化,因为例如,如果没有它绘制100行将分配1mb图像数百次。当前界面运行良好,但我觉得可能有一些标准方法来做这样的事情。所以,
修改 二进制操作的一个示例是“拍摄图像,制作模糊的副本并从原始图像中减去”。返回结果'。仅在IO monad中具有最小副本的有效版本将类似于:
poorMansHighPass img = do
x <- clone img
gaussian (5,5) x
subtract x img
return x
虽然我可以创建这样的运算符,但我更喜欢的东西更多是原始运算符的组合而不是丑陋的不安全的io代码。
答案 0 :(得分:5)
好吧,我至少可以指出你目前正在使用的一些模式。
所以我们有一个表示对某些可变数据的引用的类型,以及一个表示对它的不透明操作的类型。我们还有一个null op和一个组合函数,它提供了一个明显的Monoid
实例:
instance Monoid (ImageOperation c d) where
mempty = nonOp
mappend = (#>)
这至少可以使用一个标准名称。
此外,上述Monoid
实际上是其他两种众所周知类型属性的直接结果:
Applicative
的{{1}}和/或Monad
实例描述了通过将所有这些函数应用于单个参数来组合函数,就像组合函数中的图像一样。基本上是(->) a
monad的轻量级内联版本。
Reader
的{{1}}个实例,或者更确切地说是它所暗示的幺半群结构。通过将Monad
的类型参数固定为IO
,monad法则简化为一个简单的monoid,其中IO
为单位,()
为monoid操作。
为了重建你的组合,给定两个函数(展开return ()
s)并想象(>>)
的隐含monoid是一个实际的实例,我们可以写:
ImageOperation
同样值得注意的是,读取器monad和monad允许可变状态之类的组合实质上描述了“具有可变引用的周围环境”,也称为可变全局变量,除了“global”在这里意味着“在单个计算中”合并monad“。我实际上是使用IO ()
和nonOp = pure mempty
x #> y = mappend <$> x <*> y
明确构建了这样一个monad。
处理组合操作。要实际运行一个操作,你需要一个ReaderT
并且我正在收集你想要只对克隆进行操作,其创建效率很低。幸运的是,考虑到STM
的上述构造非常一般,在实际运行它之前,你真的没有什么东西可以塞进Image
。生成克隆可能是一个Monoid
操作,这是我假设在ImageOperation
中进行的操作 - 可能没有其他任何方法可以做到这一点。
除此之外,如果你对构建整个事物的其他方法感兴趣,一个明显的变体就是将IO
换成代表构造一个过程的东西,并将运算符合并以转换图像使用operate
之类的东西制作。不过,我不知道这是否会让你获得任何东西。
事实上,我倾向于怀疑还有其他方法可以做到这一点。你正在编写一个FFI绑定到一个高度命令性的库,你只能做很多事情来伪装它。
但是,我不确定为什么你有Image
的不安全版本。这有什么实际意义?
我也不确定你想要概括为什么类型的二元运算符 - 除了你在这里之外,你在operate
上除了你之外别无他法。你的意思是推广operate
来处理一个以上对图像的可变引用吗?或者涉及对图像进行操作的东西,这些图像返回的不仅仅是ImageOperation
?
编辑:好的,让我们来看看如何分解ImageOperation
。希望我正确地阅读它在这里做的事情:
首先,IO ()
是独立的,可以作为自己的操作进行考虑:poorMansHighPass
。
接下来,gaussian
也可以被计算出来,并通过其他gauss' = ImgOp . gaussian
subtract
进行参数化。
这两个是函数的核心,它们可以通常的方式组合:Image
。恢复原始函数所需的最后一件事是,subtr' = ImgOp . flip subtract
的{{1}}参数必须是相同的图像,其克隆通过{{1}传递给内部函数}。
首先,我们将显式展开poorMansHP' img = gauss' (5, 5) #> subtr' img
并在重新实现中使用它:
img
在此处替换poorMansHP'
代替operate
:
ImageOperation
Desugar poorMansHighPass img =
let (ImgOp op) = gauss' (5, 5) #> subtr' img
in do x <- clone img
op x
return x
区块:
withClone
...显然包含clone
的重新实现,因此请更换并简化:
poorMansHighPass img =
let (ImgOp op) = gauss' (5, 5) #> subtr' img
in withClone img $ \x -> do op x
return x
更有趣的是以修改参数而不是克隆的方式实现do
,这将允许将其打包为poorMansHighPass img =
let (ImgOp op) = gauss' (5, 5) #> subtr' img
in withClone img $ \x -> op x >> return x
本身。可能这是它应该做的,我误读了你的代码?
无论如何,重构的基本结构是相同的,但是你需要一个不同的组合运算符 - 而不是依次将两个运算符应用于同一个输入,它需要在内部创建一个输入的克隆在重新组合结果之前。我粗略地了解了哪种结构可以使这项工作顺利进行,如果您愿意,我可以详细说明,但我必须稍微努力以确保其正常运行。
答案 1 :(得分:2)
Here是我使用OpenCV代码的方法。我从来没有看到它超出了我正在进行的几个项目的需要,我认为这种方法应该伴随性能回归测试。