alpha融合详解(alpha compositing)

alpha融合简介

alpha融合(alpha compositing)是图像处理中常用技术,常见的公式如下:
C O = α C A + ( 1 − α ) C B (1) C_O= \alpha C_A + (1-\alpha)C_B \tag{1} CO=αCA+(1α)CB(1)
其中 C A C_A CA C B C_B CB表示待融合颜色(RGB), C O C_O CO表示输出颜色, α \alpha α表示融合因子,取值[0, 1]。

习惯上 C A C_A CA表示前景, C B C_B CB表示背景。 α \alpha α用于表征前景颜色 C A C_A CA在结果中的可见度(透明度), α \alpha α越大,表示 C A C_A CA在结果中占得比重越大。当 α = 1 \alpha=1 α=1时,结果完全等于 C A C_A CA(完全不透明);反之当 α = 0 \alpha=0 α=0时, C A C_A CA在结果中完全不可见(完全透明)。

alpha融合提出之初,用于解决在计算机的辅助下,动画片生产中的素材融合问题,为了处理有交叠的素材之间的透明度关系而发明了这项技术。

最早的alpha融合就是公式(1),由Smith等在1970s提出,也是我们各类教材中最常见的公式。该公式最大的特点就是只有一个 α \alpha α,一般我们认为该 α \alpha α用于描述 C A C_A CA的透明度,而背景 C B C_B CB的透明度则默认是1,且必须是1。但是,如果待融合的两个颜色各自都有自己的 α \alpha α,即存在 α A \alpha_A αA α B \alpha_B αB,那么上述公式就不适用了。后来Wallace在1981提出了一个更加通用的公式,用于解决 α A \alpha_A αA α B \alpha_B αB同时存在的情况。直到此时,学者们都是在处理素材在自然叠放情况下的透明度关系,也就是所谓的“over”模式,即A over B。后来Porter 1984年进一步推广了这项技术,提出了alpha预乘(premultiplied alpha)的概念,并发展了如in, out, atop, xor等其他几种融合模式。

但无论技术本身如何发展,应用alpha融合时必须时刻记住一个大前提:为了获得最终屏幕渲染的结果,必定存在一个完全不透明的“背景”。这个背景可以是诸如ppt中的白色底板,也可以是Photoshop中的灰色格子。因为 α \alpha α并不是一个物理性的东西,而是为了处理透明度关系而发明出来的一个用于辅助计算的概念性的东西。我们的显示器中没有用于显示 α \alpha α的物理元器件,显示器只能显示颜色。因此无论有多少个素材,它们有多么复杂的透明度以及交叠关系,最终必需都得落在一个完全不透明的背景上,得到纯粹的颜色信息,才能送给显示器去上屏显示。

如下图中的灰色格子就是上面所提的“完全不透明的背景”。
在这里插入图片描述

认识alpha通道

现在一些图像格式可以支持(R,G,B,A)四个通道,最典型的如png图像,这其中A就是所谓的alpha通道,用于控制颜色信息(R,G,B)的透明度,因此有时我们会把alpha通道称为透明度通道。

如下图是一个简单的素材,左图是RGBA,中间是RGB,右图是alpha(A),alpha通道越亮表示数值越大。
可以看到,该图像的RGB颜色在所有位置都相同,但我们可以通过alpha通道来调整图像各区域相对于背景的透明度情况。
在这里插入图片描述

人像抠图一般也是通过alpha通道来实现。下方素材使用开源数据集FFHQ中的00148.png,同样左图是RGBA,中间是RGB,右图是alpha(A)。
现在抠图的mask(即alpha通道)一般通过卷积神经网络分割算法实现,分割算法的输出是一个概率值,一般由sigmoid函数映射得到。mask中白色表示几乎100%可见,黑色表示几乎0%可见。由于sigmoid函数的值域是(0, 1),也就是说除了几乎纯白与纯黑之外,还会有0.2,0.5之类的灰度数值,这些数值分布在纯白与纯黑的过渡区域,天然形成了羽化的边缘,可使得分割结果具有抗锯齿的效果。
在这里插入图片描述
在融合时,如果对抠图的alpha做了不正确的处理,常常会在边缘引起一些badcase,如典型的白边黑边问题,锯齿问题。比如,若我们抠像使用的mask是一个二值化,非0即1的结果,那么就会引起上述问题。
如果我们将上图中mask不等于0的地方全部置为1(if mask >0: maks = 1),然后用于抠像,并置于一个绿色背景上面,可以得到下图。
下图中,左图是结果,中间是原图,右图是二值化mask。
放大下面左图可以明显看到锯齿,头发抠图不够细致,存在多余的边缘信息等问题。
请添加图片描述

我们将二值化的mask与正常mask相减,就可以更清楚地感受到灰度过渡边缘的存在:
下图中,左图是二值化mask,中间是正常mask,右图是二值化减去正常mask得到的灰度边缘。
请添加图片描述

alpha compositing:over模式

Over模式就是一层一层的叠放,放置于上面的素材会一定程度遮挡放置于下方的素材,遮挡程度由上面素材的透明度决定。

首先定义一个表示方法:如果有素材A( α A \alpha_{A} αA),B( α B \alpha_{B} αB),C( α C \alpha_{C} αC),…,以及背景Z( α Z ≡ 1 \alpha_{Z} \equiv 1 αZ1),那么AZ表示将素材A叠放在背景Z之上,ABZ表示将B叠放在Z上面,再将A叠放在B上面,以此类推。

以公式(1)的方式,有:
C A B Z = C A ( B Z ) = α A C A + ( 1 − α A ) C B Z = α A C A + ( 1 − α A ) ( α B C B + ( 1 − α B ) C Z ) = α A C A + ( 1 − α A ) α B C B + ( 1 − α A ) ( 1 − α B ) C Z (2) \begin{aligned} C_{ABZ} &= C_{A(BZ)} \\[2ex] &= \alpha_{A} C_A + (1-\alpha_{A})C_{BZ} \\[2ex] &= \alpha_{A} C_A + (1-\alpha_{A})(\alpha_{B} C_B + (1-\alpha_{B})C_Z) \\[2ex] &= \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B + (1-\alpha_{A}) (1-\alpha_{B})C_Z \end{aligned} \tag{2} CABZ=CA(BZ)=αACA+(1αA)CBZ=αACA+(1αA)(αBCB+(1αB)CZ)=αACA+(1αA)αBCB+(1αA)(1αB)CZ(2)

公式(1)有一个限制,那就是,谁处于融合背景的位置,谁的alpha就必须等于1,比如上式中,当BZ融合之后,也就是把B放在一个完全不透明的Z之上,所得结果肯定是完全不透明的,此时再次利用公式(1)将A叠放上去才能得到正确结果。因此在这个限制之下,我们必须从背景开始一层一层向上融合,才能得到正确结果,这就给融合带来了很大的不灵活性。比如我们已有一个素材池,其中素材均带有透明度信息,现在要融合其中多个素材得到一个带透明度信息的新素材,在这个任务根本没有“背景”这个东西,所以此时通过公式(1)根本无法完成任务。再比如,如果我们想把A和B都做相同的旋转,那么根据公式(1),我们必须先把B旋转,然后放置于Z上面,再把A旋转,然后放置于BZ上面,也就是说尽管A和B的旋转是相同的,但我们还是必须做两次旋转操作。

究其原因,是因为公式(1)中只允许一个 α \alpha α不等于1,作为背景的 α \alpha α必须等于1。现在我们希望待融合的两个对象均可以有不等于1的 α \alpha α

此时融合结果可以表示为 C A B C_{AB} CAB,其透明度表示为 α A B \alpha_{AB} αAB,这两个量目前都是未知的,最终结果可以按照如下融合过程来实现:
C A B Z = C ( A B ) Z = α A B C A B + ( 1 − α A B ) C Z (3) \begin{aligned} C_{ABZ} &= C_{(AB)Z} \\[2ex] &= \alpha_{AB} C_{AB} + (1-\alpha_{AB})C_{Z} \\[2ex] \end{aligned} \tag{3} CABZ=C(AB)Z=αABCAB+(1αAB)CZ(3)

对于任意 C Z C_Z CZ,公式(2)与(3)的结果都必须相等,所以 C Z C_Z CZ的系数相与偏置项必须分别相等,得:
α A B C A B = α A C A + ( 1 − α A ) α B C B ( 1 − α A B ) = ( 1 − α A ) ( 1 − α B ) \begin{aligned} \alpha_{AB} C_{AB} &= \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B\\[2ex] (1-\alpha_{AB}) &= (1-\alpha_{A}) (1-\alpha_{B}) \\[2ex] \end{aligned} αABCAB(1αAB)=αACA+(1αA)αBCB=(1αA)(1αB)

解得:

α A B = α A + α B − α A α B = α A + α B ( 1 − α A ) C A B = α A C A + ( 1 − α A ) α B C B α A B (4) \begin{aligned} \alpha_{AB} &= \alpha_{A} + \alpha_{B} - \alpha_{A} \alpha_{B} \\[2ex] &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) \\[4ex] C_{AB} &=\frac { \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B}{\alpha_{AB}}\\[2ex] \end{aligned} \tag{4} αABCAB=αA+αBαAαB=αA+αB(1αA)=αABαACA+(1αA)αBCB(4)

在Wallace(1981)的文章中通过反射/透射(Reflectance / Transmittance)的物理概念得到,但是也可以根据上述公式推导而得。

下图是按照公式(1)(2)的方式融合的结果,背景为纯白。
上左:绿色圆圈叠在白色背景上面,上右:红色方块叠在“上左”上面。
下左:红色方块叠在白色背景上面,下右:绿色圆圈叠在“下左”上面。
在这里插入图片描述

下图是按照公式(3)(4)的方式计算出的结果,背景同样为纯白。容易看出这种方式计算出的结果与上图结果完全相同。
左图:红色方块叠在绿色圆圈上,再叠在白色背景上。
右图:绿色圆圈叠在红色方块上,再叠在白色背景上。
在这里插入图片描述

下图仅利用公式(4)计算两个有透明度信息的素材融合的结果,并把两者的color与alpha分开显示。
左上:红色方块叠在绿色圆圈上。左下:绿色圆圈叠在红色方块上
中间列:color
右列:alpha

在这里插入图片描述
PS:上图中间列一定程度上可以看做是在一个不正确的alpha下渲染的结果,此时缺乏透明度信息带来的层次感。

alpha premultiplication(alpha 预乘)

对公式(4)稍作改变可以得到:
α A B C A B = α A C A + ( 1 − α A ) α B C B \alpha_{AB}C_{AB} = \alpha_{A} C_A + (1-\alpha_{A})\alpha_{B} C_B αABCAB=αACA+(1αA)αBCB
如果记
C X ′ = α X C X (5) C'_X=\alpha_{X} C_X \tag{5} CX=αXCX(5)

那么有:
α A B = α A + α B ( 1 − α A ) C A B ′ = C A ′ + C B ′ ( 1 − α A ) (6) \begin{aligned} \alpha_{AB} &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) \\[4ex] C'_{AB} &=C'_A + C'_B (1-\alpha_{A}) \end{aligned} \tag{6} αABCAB=αA+αB(1αA)=CA+CB(1αA)(6)

公式(5)就是alpha预乘,即使用原始色彩乘以自身的alpha,公式(6)是alpha预乘下的over模式公式。
我们在阅读文献资料时,如果做了alpha预乘,通常称之为premultiplied alpha,如果没有做,称之为straight alpha。

使用alpha预乘要特别注意:在计算的各个阶段,包括融合背景在内,对alpha预乘的处理要统一,否则得不到正确的渲染结果。

大部分图片浏览软件显示图片都是基于straight alpha,所以我们在保存图片到磁盘上时候要特别注意这一点,以straight alpha格式去写数据。如果我们写的是premultiplied alpha的结果,那么在看图软件里显示时候就会发暗。
下面左图是straight alpha,右图是premultiplied alpha,明显看得出右图发暗。
在这里插入图片描述

alpha预乘的优点

计算加速

在alpha预乘情况下, α X \alpha_X αX的计算和 C X ′ C'_X CX的计算系数相同,所以可以进一步记一个四元数为:
C X R G B A = ( C X ′ , α X ) C^{RGBA}_X = (C'_X, \alpha_X) CXRGBA=(CX,αX)
那么有:
C A B R G B A = C A R G B A + C B R G B A ( 1 − α A ) (7) C^{RGBA}_{AB} = C^{RGBA}_A + C^{RGBA}_B (1-\alpha_{A}) \tag{7} CABRGBA=CARGBA+CBRGBA(1αA)(7)

也就是说公式(6)两个计算式可以合并为公式(7)的一个计算式,这对于OpenGL这种具备四元数定义的语言来讲,可以起到计算加速的作用。

防止颜色渗漏

当我们做一些空间维度操作时,如图像插值,模糊等,如果使用未预乘的alpha,可能会造成颜色渗漏,特别是在alpha接近于0的地方。如下图是没有alpha预乘情况下对图像做模糊,原图像的色彩在alpha=0的地方是蓝色,alpha=1的地方是红色,由于alpha的作用,模糊前渲染结果只能看到红色,但模糊后明显可以看到有蓝色渗漏,这就不太符合直觉。
在这里插入图片描述

而如果在alpha预乘的情况下做模糊,最后的结果看起来就正常得多(下图)。
在这里插入图片描述

alpha预乘的缺点

色彩精度丢失

目前绝大多数常用色图片或者视频格式对于色彩的记录都是量化的,如SDR的8bit,HDR的10bit等。 alpha预乘后,再转为量化结果进行保存,会导致色彩精度的丢失,alpha数值越小,色彩精度的丢失就越严重,极限情况下,当alpha=0时,原始色彩将完全丢失。

如8bit情况下,alpha = 10,另外有三个原始色彩数值分别为145,150,155。那么三个色彩在相同的alpha下进行预乘(归一化后预乘)后为:
(145 / 255) * (10 / 255) = 0.022299
(150 / 255) * (10 / 255) = 0.023068
(155 / 255) * (10 / 255) = 0.023837

结果仍然要以8bit存储,所以四舍五入转为8bit数值为:
round(0.022299 * 255) = round(5.68) = 6
round(0.023068 * 255) = round(5.88) = 6
round(0.023837 * 255) = round(6.08) = 6

可以看到原本有区分度的色彩在alpha预乘下变为了同一个数字,不再有区分度。

在很多情况下,由于小的alpha会导致透明度提升,即可见度下降,所以颜色损失一些精度不会在视觉观感上带来强烈的差异。但是,如果我们面对的是调色工作,一般不能在alpha预乘的情况下去实施,因为对于大多数情况,我们想要的都是下式的左边(先调色再alpha预乘),它与右边(先alpha预乘再调色)一般情况下不相等。
α ∗ A d j u s t ( c o l o r ) ≠ A d j u s t ( α ∗ c o l o r ) \alpha*Adjust(color) \ne Adjust(\alpha*color) αAdjust(color)=Adjust(αcolor)

如果我们拿到的输入是做过alpha预乘的,在调色前应当做逆alpha预乘,做法是:

if alpha != 0:
	color = color / alpha
else:
	color = 0

alpha compositing over模式的结合律

当有多个带透明度通道的素材时,在公式(1)的情况下,只能自底(画布)而上一层一层融合才能得到正确的结果。而在公式(4)或(6)的情况下,只要叠放顺序不变,我们可以任意决定融合的优先级,最后都能得到正确的结果。这就带来了很大的便利性,不仅允许我们使用多个透明度素材通过融合生成新的透明度素材,而且可以节省一些共同计算。比如AB两个素材需要做相同旋转,在公式(1)情况下必需先对B做旋转,融合到画布上,再对A做旋转,再次融合到画布上,此时需要做两次旋转;而如果使用公式(4)(6),可以先融合AB,再做一次旋转,再融合到画布上,此时只需要做一次旋转。

下面用公式(6)做一个简单的结合律推导,对比下面的公式(8)(9)可知 C A ( B C ) ′ = C ( A B ) C ′ C'_{A(BC)} = C'_{(AB)C} CA(BC)=C(AB)C,即交换ABC素材融合的优先级,结果不变。
α A ( B C ) = α A + α B C ( 1 − α A ) = α A + [ α B + α C ( 1 − α B ) ] ( 1 − α A ) = α A + α B ( 1 − α A ) + α C ( 1 − α A ) ( 1 − α B ) C A ( B C ) ′ = C A ′ + C B C ′ ( 1 − α A ) = C A ′ + [ C B ′ + C C ′ ( 1 − α B ) ] ( 1 − α A ) = C A ′ + C B ′ ( 1 − α A ) + C C ′ ( 1 − α A ) ( 1 − α B ) (8) \begin{aligned} \alpha_{A(BC)} &= \alpha_{A} + \alpha_{BC}(1-\alpha_{A}) \\[2ex] &=\alpha_{A} + [ \alpha_{B} + \alpha_{C}(1-\alpha_{B})](1-\alpha_{A}) \\[2ex] &= \alpha_{A} + \alpha_{B} (1-\alpha_{A}) + \alpha_{C}(1-\alpha_{A})(1-\alpha_{B}) \\[4ex] C'_{A(BC)} &= C'_{A} + C'_{BC} (1-\alpha_{A}) \\[2ex] &= C'_{A} + [ C'_{B} + C'_{C} (1-\alpha_{B}) ] (1-\alpha_{A}) \\[2ex] &= C'_{A} + C'_{B} (1-\alpha_{A}) + C'_{C} (1-\alpha_{A})(1-\alpha_{B}) \end{aligned} \tag{8} αA(BC)CA(BC)=αA+αBC(1αA)=αA+[αB+αC(1αB)](1αA)=αA+αB(1αA)+αC(1αA)(1αB)=CA+CBC(1αA)=CA+[CB+CC(1αB)](1αA)=CA+CB(1αA)+CC(1αA)(1αB)(8)

α ( A B ) C = α A B + α C ( 1 − α A B ) = α A + α B ( 1 − α A ) + α C ( 1 − [ α A + α B ( 1 − α A ) ] ) = α A + α B ( 1 − α A ) + α C ( 1 − α A ) ( 1 − α B ) C ( A B ) C ′ = C A B ′ + C C ′ ( 1 − α A B ) = C A ′ + C B ′ ( 1 − α A ) + C C ′ ( 1 − [ α A + α B ( 1 − α A ) ] ) = C A ′ + C B ′ ( 1 − α A ) + C C ′ ( 1 − α A ) ( 1 − α B ) (9) \begin{aligned} \alpha_{(AB)C} &= \alpha_{AB} + \alpha_{C}(1-\alpha_{AB}) \\[2ex] &= \alpha_{A} + \alpha_{B}(1-\alpha_{A}) + \alpha_{C}(1-[ \alpha_{A} + \alpha_{B}(1-\alpha_{A})]) \\[2ex] &= \alpha_{A} + \alpha_{B} (1-\alpha_{A}) + \alpha_{C}(1-\alpha_{A})(1-\alpha_{B}) \\[4ex] C'_{(AB)C} &= C'_{AB} + C'_{C} (1-\alpha_{AB}) \\[2ex] &= C'_{A} + C'_{B}(1-\alpha_{A}) + C'_{C}(1-[ \alpha_{A} + \alpha_{B}(1-\alpha_{A})]) \\[2ex] &= C'_{A} + C'_{B} (1-\alpha_{A}) + C'_{C} (1-\alpha_{A})(1-\alpha_{B}) \end{aligned} \tag{9} α(AB)CC(AB)C=αAB+αC(1αAB)=αA+αB(1αA)+αC(1[αA+αB(1αA)])=αA+αB(1αA)+αC(1αA)(1αB)=CAB+CC(1αAB)=CA+CB(1αA)+CC(1[αA+αB(1αA)])=CA+CB(1αA)+CC(1αA)(1αB)(9)

其他模式的alpha compositing

其他还有四种不太常用的alpha compositing模式,分别是in,out,atop,xor。Porter(1984)根据光的反射和透射概念对上述模式进行了定义。

下图是wikipedia中对各种模式的示例
在这里插入图片描述
Wikipedia中没有给计算公式,计算需要参考Porter(1984)的文献,如下:
在这里插入图片描述

在这里插入图片描述

统一的公式是(straight alpha):
α A B = α A F A + α B F B C A B =   α A F A C A + α B F B C B α A B (10) \begin{aligned} \alpha_{AB} &= \alpha_{A}F_{A} + \alpha_{B}F_{B} \\[4ex] C_{AB} &=\frac { \ \alpha_{A} F_{A} C_{A} + \alpha_{B} F_{B} C_{B}} {\alpha_{AB}}\\[2ex] \end{aligned} \tag{10} αABCAB=αAFA+αBFB=αAB αAFACA+αBFBCB(10)

F的数值如下:
over: F A = 1 , F B = 1 − α A F_A=1, F_B=1-\alpha_A FA=1,FB=1αA
in: F A = α B , F B = 0 F_A = \alpha_B, F_B=0 FA=αB,FB=0
out: F A = 1 − α B , F B = 0 F_A=1-\alpha_B,F_B=0 FA=1αB,FB=0
atop: F A = α B , F B = 1 − α A F_A=\alpha_B,F_B=1-\alpha_A FA=αB,FB=1αA
xor: F A = 1 − α B , F B = 1 − α A F_A=1-\alpha_B,F_B=1-\alpha_A FA=1αB,FB=1αA

下图从左到右分别是in,out,atop,xor的结果,上面一排没有底层画布,下面一排使用了格子画布,方便观察和对比效果。
在这里插入图片描述

实验代码

utils.py

# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np


def remove_file(filename):
    """
    Remove a file.
    """
    if os.path.exists(filename):
        os.remove(filename)


def float_to_uint8(image, scale=255.):
    return np.uint8(np.clip(np.round(image * scale), 0, 255))


def concat_color_alpha(color, alpha):
    alpha = np.squeeze(alpha)[..., np.newaxis]
    image = np.concatenate((color, alpha), axis=2)
    return image


def alpha_premultiply(image, alpha):
    alpha = np.squeeze(alpha)
    result = image * alpha[..., np.newaxis]
    return result


def mix(color1, color2, alpha):
    """
    Naive alpha blending.
    """
    if not isinstance(alpha, (int, float)):
        alpha = np.squeeze(alpha)[..., np.newaxis]
    res = color1 * (1.0 - alpha) + color2 * alpha
    return res


def alpha_compositing(fg_color,
                      fg_alpha,
                      bg_color,
                      bg_alpha,
                      mode='over',
                      premultiplied_input=False,
                      premultiplied_output=False):
    if not isinstance(fg_alpha, (int, float)):
        fg_alpha = np.squeeze(fg_alpha)[..., np.newaxis]
    if not isinstance(bg_alpha, (int, float)):
        bg_alpha = np.squeeze(bg_alpha)[..., np.newaxis]

    if mode == 'over':
        fg_fraction = 1.
        bg_fraction = 1. - fg_alpha
    elif mode == 'in':
        fg_fraction = bg_alpha
        bg_fraction = 0.
    elif mode == 'out':
        fg_fraction = 1. - bg_alpha
        bg_fraction = 0.
    elif mode == 'atop':
        fg_fraction = bg_alpha
        bg_fraction = 1. - fg_alpha
    elif mode == 'xor':
        fg_fraction = 1. - bg_alpha
        bg_fraction = 1. - fg_alpha
    else:
        raise ValueError(
            "mode must be one of ['over', 'in', 'out', 'atop', 'xor']")

    out_alpha = fg_alpha * fg_fraction + bg_alpha * bg_fraction
    if premultiplied_input:
        out_color = fg_fraction * fg_color + bg_fraction * bg_color
    else:
        out_color = fg_alpha * fg_fraction * fg_color + \
                    bg_alpha * bg_fraction * bg_color

    if not premultiplied_output:
        out_color = out_color / np.maximum(out_alpha, 1e-6)

    return out_color, out_alpha

media.py

# -*- coding: utf-8 -*-
import cv2
import numpy as np

from utils import float_to_uint8
from utils import concat_color_alpha


def decomposite_00148():
    image_wo_bg = cv2.imread(r'portrait\00148-remove-bg-small.png',
                             cv2.IMREAD_UNCHANGED)
    height, width = image_wo_bg.shape[:2]
    ori_image = cv2.imread(r'.\portrait\00148.png', cv2.IMREAD_UNCHANGED)
    image = cv2.resize(ori_image, dsize=(width, height), fx=None, fy=None,
                       interpolation=cv2.INTER_AREA)

    color = image_wo_bg[..., :3]
    alpha = image_wo_bg[..., -1]

    binary_alpha = alpha.copy()
    binary_alpha[binary_alpha > 0] = 255
    alpha_edge = binary_alpha - alpha

    image_small = concat_color_alpha(image, alpha)

    cv2.imwrite(r'.\portrait\00148-small.png', image)
    cv2.imwrite(r'.\portrait\color.png', color)
    cv2.imwrite(r'.\portrait\alpha.png', alpha)
    cv2.imwrite(r'.\portrait\binary-alpha.png', binary_alpha)
    cv2.imwrite(r'.\portrait\alpha-edge.png', alpha_edge)
    cv2.imwrite(r'.\portrait\00148-remove-bg-small-new.png', image_small)


def generate_ellipse_media():
    height = 600
    width = 800

    alpha = np.zeros(shape=[height, width], dtype=np.float_)
    half_min_size = int(min(width, height) // 2)
    cv2.ellipse(alpha,
                center=(width // 2, height // 2),
                axes=(int(half_min_size * 0.8), int(half_min_size * 0.6)),
                angle=0,
                startAngle=0,
                endAngle=360,
                color=1.0,
                thickness=-1,
                lineType=cv2.LINE_AA)

    result = np.ones(shape=(height, width, 4), dtype=np.float_)
    index = alpha > 0.99
    result[index, :3] = (0., 0., 1.)
    result[~index, :3] = (1., 0., 0.)
    result[..., -1] = alpha
    cv2.imwrite(r'.\media\ellipse.png', float_to_uint8(result))


def generate_circle_media():
    fg_color = (0., 1., 0.)
    height = 600
    width = 800
    half_min_size = int(min(width, height) // 2)

    alpha = np.zeros(shape=[height, width], dtype=np.float_)
    cv2.circle(alpha,
               center=(width // 2 + 50, height // 2 + 80),
               radius=int(half_min_size * 0.55),
               color=1.0,
               thickness=-1,
               lineType=cv2.LINE_AA)

    radius = int(half_min_size * 0.3) // 2 * 2 + 1
    alpha = cv2.GaussianBlur(alpha,
                             ksize=(radius, radius),
                             sigmaX=radius / 2.5,
                             sigmaY=radius / 2.5,
                             borderType=cv2.BORDER_REFLECT_101)
    alpha = alpha / np.max(alpha)
    alpha = 0.2 + alpha * 0.4

    result = np.ones(shape=(height, width, 4), dtype=np.float_)
    result[..., :3] = fg_color
    result[..., -1] = alpha
    result = float_to_uint8(result)
    cv2.imwrite(r'.\media\green-circle.png', result)
    cv2.imwrite(r'.\media\green-circle-color.png', result[..., :3])
    cv2.imwrite(r'.\media\green-circle-alpha.png', result[..., -1])

    result[..., :3] = result[..., :3] * alpha[..., np.newaxis]
    cv2.imwrite(r'.\media\green-circle-premultiply.png', result)
    cv2.imwrite(r'.\media\green-circle-premultiply-color.png', result[..., :3])


def generate_square_media():
    fg_color = (0., 0., 1.)
    height = 600
    width = 800
    half_min_size = int(min(width, height) // 2)

    alpha = np.zeros(shape=[height, width], dtype=np.float_)
    base_square_points = np.array([
        [0, 0],
        [0, 1],
        [1, 1],
        [1, 0]
    ])
    offset = (half_min_size // 2, half_min_size // 4)
    edge_size = half_min_size
    square_points = base_square_points * edge_size + offset

    cv2.fillPoly(alpha, [square_points], 1.0, lineType=cv2.LINE_AA)

    radius = int(half_min_size * 0.3) // 2 * 2 + 1
    alpha = cv2.boxFilter(alpha, ddepth=-1, ksize=(radius, radius),
                          borderType=cv2.BORDER_REFLECT_101)
    alpha = alpha / np.max(alpha)
    alpha = 0.2 + alpha * 0.3

    result = np.ones(shape=(height, width, 4), dtype=np.float_)
    result[..., :3] = fg_color
    result[..., -1] = alpha
    result = float_to_uint8(result)
    cv2.imwrite(r'.\media\red-square.png', result)
    cv2.imwrite(r'.\media\red-square-color.png', result[..., :3])
    cv2.imwrite(r'.\media\red-square-alpha.png', result[..., -1])


def read_portrait_media():
    scale = 255.
    color = cv2.imread(r'.\portrait\color.png', cv2.IMREAD_UNCHANGED)
    alpha = cv2.imread(r'.\portrait\alpha.png', cv2.IMREAD_UNCHANGED)
    binary_alpha = cv2.imread(r'.\portrait\binary-alpha.png',
                              cv2.IMREAD_UNCHANGED)

    color = color / scale
    alpha = alpha / scale
    binary_alpha = binary_alpha / scale
    return color, alpha, binary_alpha


def read_color_media():
    scale = 255.
    red_square = cv2.imread(r'.\media\red-square.png', cv2.IMREAD_UNCHANGED)
    green_circle = cv2.imread(r'.\media\green-circle.png',
                              cv2.IMREAD_UNCHANGED)
    red_square = red_square / scale
    green_circle = green_circle / scale
    return red_square, green_circle


def read_ellipse_media():
    scale = 255.
    image = cv2.imread(r'.\media\ellipse.png', cv2.IMREAD_UNCHANGED)
    image = image / scale
    return image

main.py

# -*- coding: utf-8 -*-
import cv2
import numpy as np

from utils import float_to_uint8
from utils import alpha_premultiply
from utils import concat_color_alpha
from utils import mix
from utils import alpha_compositing

from media import decomposite_00148
from media import generate_circle_media
from media import generate_square_media
from media import generate_ellipse_media

from media import read_ellipse_media
from media import read_portrait_media
from media import read_color_media


def alpha_premultiply_00148():
    color, alpha, binary_alpha = read_portrait_media()

    # premultiply alpha
    premultiply_alpha = alpha_premultiply(color, alpha)
    premultiply_alpha = float_to_uint8(premultiply_alpha)
    cv2.imwrite(r'.\portrait\premultiply-alpha.png', premultiply_alpha)

    # premultiply binary alpha
    premultiply_binary_alpha = alpha_premultiply(color, binary_alpha)
    premultiply_binary_alpha = float_to_uint8(premultiply_binary_alpha)
    cv2.imwrite(r'.\portrait\premultiply-binary-alpha.png',
                premultiply_binary_alpha)


def alpha_premultiply_leak():
    image = read_ellipse_media()
    height, width = image.shape[:2]
    half_min_size = int(min(width, height) // 2)
    radius = int(half_min_size * 0.3) // 2 * 2 + 1
    straight_blur = cv2.GaussianBlur(image,
                                     ksize=(radius, radius),
                                     sigmaX=radius / 2.5,
                                     sigmaY=radius / 2.5,
                                     borderType=cv2.BORDER_REFLECT_101)
    straight_blur = float_to_uint8(straight_blur)

    premultiplied_image = image.copy()
    premultiplied_image[..., :3] = image[..., :3] * image[..., -1:]
    premultiplied_blur = cv2.GaussianBlur(premultiplied_image,
                                          ksize=(radius, radius),
                                          sigmaX=radius / 2.5,
                                          sigmaY=radius / 2.5,
                                          borderType=cv2.BORDER_REFLECT_101)
    premultiplied_blur = float_to_uint8(premultiplied_blur)

    image = float_to_uint8(image)
    premultiplied_image = float_to_uint8(premultiplied_image)

    cv2.imwrite(r'.\media\ellipse-straight-color.png', image[..., :3])
    cv2.imwrite(r'.\media\ellipse-alpha.png', image[..., -1])
    cv2.imwrite(r'.\media\ellipse-straight-blur.png', straight_blur)
    cv2.imwrite(r'.\media\ellipse-straight-blur-color.png',
                straight_blur[..., :3])
    cv2.imwrite(r'.\media\ellipse-blur-alpha.png',
                straight_blur[..., -1])
    cv2.imwrite(r'.\media\ellipse-premultiplied-color.png',
                premultiplied_image[..., :3])
    cv2.imwrite(r'.\media\ellipse-premultiplied-blur-color.png',
                premultiplied_blur[..., :3])

    premultiplied_blur = premultiplied_blur / 255.
    index = premultiplied_blur[..., -1] > 0
    premultiplied_blur[index, :3] = premultiplied_blur[index, :3] / \
                                    premultiplied_blur[index, -1:]
    premultiplied_blur = float_to_uint8(premultiplied_blur)
    cv2.imwrite(r'.\media\ellipse-premultiplied-blur.png', premultiplied_blur)


def naive_alpha_blending():
    color, alpha, binary_alpha = read_portrait_media()

    bg_color = np.array([0, 1., 0.])

    # naive alpha blending using normal alpha
    alpha_blending = mix(bg_color, color, alpha)
    alpha_blending = float_to_uint8(alpha_blending)
    cv2.imwrite(r'.\portrait\alpha-blending-with-bg.png', alpha_blending)

    # naive alpha blending using binary_alpha
    binary_alpha_blending = mix(bg_color, color, binary_alpha)
    binary_alpha_blending = float_to_uint8(binary_alpha_blending)
    cv2.imwrite(r'.\portrait\binary-alpha-blending-with-bg.png',
                binary_alpha_blending)


def alpha_blending_color_media():
    red_square, green_circle = read_color_media()
    bg_color = (1., 1., 1.)

    bg_green = mix(bg_color, green_circle[..., :3], green_circle[..., -1])
    bg_green_red = mix(bg_green, red_square[..., :3], red_square[..., -1])
    bg_green = float_to_uint8(bg_green)
    bg_green_red = float_to_uint8(bg_green_red)

    bg_red = mix(bg_color, red_square[..., :3], red_square[..., -1])
    bg_red_green = mix(bg_red, green_circle[..., :3], green_circle[..., -1])
    bg_red = float_to_uint8(bg_red)
    bg_red_green = float_to_uint8(bg_red_green)

    cv2.imwrite(r'.\media\alpha-blending-bg-green.png', bg_green)
    cv2.imwrite(r'.\media\alpha-blending-bg-green-red.png', bg_green_red)
    cv2.imwrite(r'.\media\alpha-blending-bg-red.png', bg_red)
    cv2.imwrite(r'.\media\alpha-blending-bg-red-green.png', bg_red_green)


def alpha_compositing_over_portrait():
    color, alpha, binary_alpha = read_portrait_media()

    bg_color = np.array([0, 1., 0.])
    bg_alpha = 1.0

    # composite - 0: alpha compositing, no premultiply, bg_alpha = 1.0
    composited_color, composited_alpha = alpha_compositing(
        color, alpha, bg_color, bg_alpha)
    image = concat_color_alpha(composited_color, composited_alpha)
    image = float_to_uint8(image)
    cv2.imwrite(r'.\portrait\alpha-compositing-over-bg-alpha=1.0.png', image)

    # composite - 0: alpha compositing, no premultiply, bg_alpha = 0.5
    bg_alpha = 0.5
    composited_color, composited_alpha = alpha_compositing(
        color, alpha, bg_color, bg_alpha)
    image = concat_color_alpha(composited_color, composited_alpha)
    image = float_to_uint8(image)
    cv2.imwrite(r'.\portrait\alpha-compositing-over-bg-alpha=0.5.png', image)


def alpha_compositing_over_color_media():
    red_square, green_circle = read_color_media()
    bg_color = (1., 1., 1.)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1])
    red_over_green = concat_color_alpha(color, alpha)
    red_over_green_over_bg, _ = alpha_compositing(red_over_green[..., :3],
                                                  red_over_green[..., -1],
                                                  bg_color,
                                                  1.0)

    red_over_green = float_to_uint8(red_over_green)
    red_over_green_over_bg = float_to_uint8(red_over_green_over_bg)
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green.png',
                red_over_green)
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green-color.png',
                red_over_green[..., :3])
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green-alpha.png',
                red_over_green[..., -1])
    cv2.imwrite(r'.\media\alpha-compositing-red-over-green_over_bg.png',
                red_over_green_over_bg)

    color, alpha = alpha_compositing(green_circle[..., :3],
                                     green_circle[..., -1],
                                     red_square[..., :3],
                                     red_square[..., -1])
    green_over_red = concat_color_alpha(color, alpha)
    green_over_red_over_bg, _ = alpha_compositing(green_over_red[..., :3],
                                                  green_over_red[..., -1],
                                                  bg_color,
                                                  1.0)

    green_over_red = float_to_uint8(green_over_red)
    green_over_red_over_bg = float_to_uint8(green_over_red_over_bg)
    cv2.imwrite(r'.\media\alpha-compositing-green-over-red.png',
                green_over_red)
    cv2.imwrite(r'.\media\alpha-compositing-green-over-red-color.png',
                green_over_red[..., :3])
    cv2.imwrite(r'.\media\alpha-compositing-green-over-red-alpha.png',
                green_over_red[..., -1])
    cv2.imwrite(r'.\media\alpha-compositing-green_over_red_over_bg.png',
                green_over_red_over_bg)


def alpha_compositing_other_modes():
    red_square, green_circle = read_color_media()

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='in')
    red_in_green = concat_color_alpha(color, alpha)
    red_in_green = float_to_uint8(red_in_green)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='out')
    red_out_green = concat_color_alpha(color, alpha)
    red_out_green = float_to_uint8(red_out_green)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='atop')
    red_atop_green = concat_color_alpha(color, alpha)
    red_atop_green = float_to_uint8(red_atop_green)

    color, alpha = alpha_compositing(red_square[..., :3],
                                     red_square[..., -1],
                                     green_circle[..., :3],
                                     green_circle[..., -1],
                                     mode='xor')
    red_xor_green = concat_color_alpha(color, alpha)
    red_xor_green = float_to_uint8(red_xor_green)

    cv2.imwrite(r'.\media\red_in_green.png', red_in_green)
    cv2.imwrite(r'.\media\red_out_green.png', red_out_green)
    cv2.imwrite(r'.\media\red_atop_green.png', red_atop_green)
    cv2.imwrite(r'.\media\red_xor_green.png', red_xor_green)


if __name__ == '__main__':
    decomposite_00148()
    generate_circle_media()
    generate_square_media()
    generate_ellipse_media()
    alpha_premultiply_00148()
    alpha_premultiply_leak()
    naive_alpha_blending()
    alpha_blending_color_media()
    alpha_compositing_over_portrait()
    alpha_compositing_over_color_media()
    alpha_compositing_other_modes()

clear.py

# -*- coding: utf-8 -*-
from utils import remove_file

if __name__ == '__main__':
    # generated by media.py
    remove_file(r'.\portrait\00148-small.png')
    remove_file(r'.\portrait\color.png')
    remove_file(r'.\portrait\alpha.png')
    remove_file(r'.\portrait\binary-alpha.png')
    remove_file(r'.\portrait\alpha-edge.png')
    remove_file(r'.\portrait\00148-remove-bg-small-new.png')

    remove_file(r'.\media\ellipse.png')

    remove_file(r'.\media\green-circle.png')
    remove_file(r'.\media\green-circle-color.png')
    remove_file(r'.\media\green-circle-alpha.png')
    remove_file(r'.\media\green-circle-premultiply.png')
    remove_file(r'.\media\green-circle-premultiply-color.png')

    remove_file(r'.\media\red-square.png')
    remove_file(r'.\media\red-square-color.png')
    remove_file(r'.\media\red-square-alpha.png')

    # generated by main.py
    remove_file(r'.\portrait\premultiply-alpha.png')
    remove_file(r'.\portrait\premultiply-binary-alpha.png')

    remove_file(r'.\media\ellipse-straight-color.png')
    remove_file(r'.\media\ellipse-alpha.png')
    remove_file(r'.\media\ellipse-straight-blur.png')
    remove_file(r'.\media\ellipse-straight-blur-color.png')
    remove_file(r'.\media\ellipse-blur-alpha.png')
    remove_file(r'.\media\ellipse-premultiplied-color.png')
    remove_file(r'.\media\ellipse-premultiplied-blur.png')
    remove_file(r'.\media\ellipse-premultiplied-blur-color.png')

    remove_file(r'.\portrait\alpha-blending-with-bg.png')
    remove_file(r'.\portrait\binary-alpha-blending-with-bg.png')
    remove_file(r'.\portrait\alpha-compositing-over-bg-alpha=1.0.png')
    remove_file(r'.\portrait\alpha-compositing-over-bg-alpha=0.5.png')
    remove_file(r'.\media\alpha-compositing-red-over-green.png')
    remove_file(r'.\media\alpha-compositing-red-over-green-color.png')
    remove_file(r'.\media\alpha-compositing-red-over-green-alpha.png')
    remove_file(r'.\media\alpha-compositing-red-over-green_over_bg.png')
    remove_file(r'.\media\alpha-compositing-green-over-red.png')
    remove_file(r'.\media\alpha-compositing-green-over-red-color.png')
    remove_file(r'.\media\alpha-compositing-green-over-red-alpha.png')
    remove_file(r'.\media\alpha-compositing-green_over_red_over_bg.png')
    remove_file(r'.\media\alpha-blending-bg-green.png')
    remove_file(r'.\media\alpha-blending-bg-green-red.png')
    remove_file(r'.\media\alpha-blending-bg-red.png')
    remove_file(r'.\media\alpha-blending-bg-red-green.png')

    remove_file(r'.\media\red_in_green.png')
    remove_file(r'.\media\red_out_green.png')
    remove_file(r'.\media\red_atop_green.png')
    remove_file(r'.\media\red_xor_green.png')

参考资料

https://en.wikipedia.org/wiki/Alpha_compositing
https://ciechanow.ski/alpha-compositing/
https://graphics.stanford.edu/papers/merging-sig81/
https://graphics.stanford.edu/papers/merging-sig81/wallace-merging-sig81.pdf
https://dl.acm.org/doi/pdf/10.1145/964965.808606

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119415.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android打造一个高性能无限层级显示的树形控件(Android树形控件)

Android使用ListView实现一个高性能无限层级显示的树形控件: 最近公司的Android项目里有一个地方需要选择某公司的所有部门,因为手机屏幕有限所以并不能像网页那样显示树状结构,但是如果只是用列表依次显示所有的部门又会让用户很难找到想要…

Centos7部署Python3环境

一、安装Python3 ###查看是否安装Python3 Centos 7 默认安装了python 2.7.5. 因为一些命令要用它比如yum 它使用的是python2.7.5 使用 python -V 命令查看一下是否安装Python 然后使用命令 which python 查看一下Python可执行文件的位置 Python指向的是Python2.7 安装依赖 y…

Eolink Apikit 版本更新:「数据字典」功能上线、支持 MongoDB 数据库操作、金融行业私有化协议、GitLab 生成 API 文档...

🎉 新增 搭建自定义接口协议架构,支持快速适配金融行业各类型私有协议的导入、编辑和展示。 数据字典功能上线,支持以数据字典的形式管理参数枚举值; 数据库连接支持 MongoDB 数据库操作; 基于 Apikit 类型导入 API…

微服务之Eureka

文章目录 一、Eureka介绍1.Eureka的作用2.总结 二.搭建Eureka服务端步骤1.导入maven依赖2.编写启动类,添加EnableEurekaServer注解3.添加application.yml文件,编写下面的配置: 三.注册Eureka客户端服务提供者(user-service&#x…

Modelsim 使用教程(5)——Analyzing Waveforms

一、概述 Wave窗口允许我们以HDL波形和数据的形式查看仿真结果。Wave窗口被划分为多个窗格。通过单击并在任意两个窗格之间拖动该条,可以调整路径名窗格、值窗格和波形窗格的大小。 二、加载一个设计(Loading a Design) 1、打开modelsim 2、…

前端之Bootstrap框架

目录 【一】Bootstrap介绍 【二】Bootstrap引入 【1】CDN加速链接 【2】注意 【三】布局容器 【四】栅格系统 【五】栅格参数 【六】列偏移 【七】排版 标题 内联文本元素 对齐 改变大小写 引用 列表 【八】表格 基本实例 条纹状表格 带边框的表格 鼠标悬停…

[量化投资-学习笔记006]Python+TDengine从零开始搭建量化分析平台-MACD

在上一章节介绍了 EMA 均线的计算,本节主要介绍均线的进化形态之一:MACD MACD (Moving Average Convergence / Divergence) 指数平滑移动平均线。MACD 是通过计算不同时间的 EMA 的差值俩判断价格趋势。 MACD 包括 3 个值: 长短期 EMA 差值…

Ubuntu 22.04.3 LTS中安装singularity

文章目录 概要背景知识什么是singularity ? 安装流程1. 安装Go2. 下载Singularity3. 编译Singularity源代码 4. 验证安装是否成功singularity的使用安装open structure 小结 概要 这里主要记录singularity的安装和使用,安装过程中会出现相关的错误,所以…

[C/C++]数据结构 链表OJ题: 链表分割

题目描述: 现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。 解题思路: 创建两个新链表,head1,head2, 遍历原链表,把小于…

RISC-V与RISC Zero zkVM的关系

1. 引言 本文基本结构为: 编程语言背景介绍RISC-V虚拟机作为zkVM电路为何选择RISC-V? 2. 编程语言背景介绍 高级编程语言不专门针对某个架构,其便于人类编写。高级编程语言代码,经编译器编译后,会生成针对专门某架…

JAR 文件规范详解

介绍 JAR文件是基于ZIP文件格式的一种文件格式,用来将许多文件整合成一个文件。一个JAR文件本质上是包含可选目录META-INF的zip文件,可以通过命令行jar工具或者在Java平台上使用java.util.jar中的API来创建。JAR文件的命名没有严格的要求,可…

详解 SpringMVC 的 HttpMessageConverter

文章目录 前言参考资料内容1、[RequestBody ](/RequestBody )2、RequestEntity3、[ResponseBody ](/ResponseBody )4、SpringMVC处理json5、SpringMVC处理ajax6、RestController注解7、ResponseEntity 推荐读物 《Spring Boot微服务实战(第2版)》内容简介目录 前言 HttpMessag…

【Agent模型1】MemGPT: Towards LLMs as Operating Systems

论文标题:MemGPT: Towards LLMs as Operating Systems 论文作者:Charles Packer, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, Joseph E. Gonzalez (UC Berkeley) 论文原文:https://arxiv.org/abs/2310.08560 论文出处&#x…

Android Studio报错:connect refused

参考链接: https://blog.csdn.net/qq_43213783/article/details/113936012 参考文章中说报错主要是由于代理导致的,在文件->设置->外观与行为->系统设置->HTTP代理。 方法一: 查看打开代理(前提是代理可以通网&#x…

Microsoft Dynamics 365 CE 扩展定制 - 8. DevOps

在本章中,我们将介绍以下内容: 使用PowerShell导出Dynamics 365解决方案使用PowerShell部署解决方案构建解决方案层次结构修补解决方案暂存解决方案使用SolutionPackager在源代码管理中保存解决方案使用PackageDeployer将您的解决方案与配置数据打包基于解决方案版本增量触发…

CSS 滚动捕获 Scroll Snap

CSS 滚动捕获 Scroll Snap CSS 滚动捕获允许开发者通过声明一些位置(或叫作捕获位置)来创建精准控制的滚动体验. 通常来说轮播图就是这种体验的例子, 在轮播图中, 用户只能停在图 A 或者图 B, 而不能停在 A 和 B 的中间. 比如平时用淘宝或小红书, 当你上滑到下一个推荐内容时…

Linux Crontab 定时任务

crond 服务 Linux 通过 crond 服务来支持 crontab。 查看 crond 服务是否已经安装 输入下面命令确认 crond 服务是否已安装。 systemctl list-unit-files | grep crond 如果为 enabled,表示服务正运行。 crontab 文件 crontab 要执行的定时任务都被保存在 /etc…

Vue3:解决基地址不同 数据交互http与https跨域问题

配置公共管理的api文件和vue.config.js可以解决跨域问题。一个项目对接不同的基地址和接口同理。 api export default {//接口基地址Millia: process.env.NODE_ENV development ? location.protocol // location.host /milliaApi : http://xx.xxx.xxxx/index.php/,Milli…

CSS 浮动

目标target✓ 能够说出来为什么需要浮动能够说出来浮动的排列特性能够说出来三种最常见的布局方式能够说出来为什么需要清除浮动,能够至少写出两种清楚浮动的方法能够利用Photoshop实现基本的切图能够利用Photoshop插件实现切图能够完成学成在线的页面布 传统网页布局的三种模…

视频增强和修复工具 Topaz Video AI mac中文版功能

Topaz Video AI mac是一款使用人工智能技术对视频进行增强和修复的软件。它可以自动降噪、去除锐化、减少压缩失真、提高清晰度等等。Topaz Video AI可以处理各种类型的视频,包括低分辨率视频、老旧影片、手机录制的视频等。 使用Topaz Video AI非常简单&#xff0c…