色度成功的关键


23

RGB颜色值#00FF00是一个相当重要的值:它用于制作电影,电视节目,天气通告等。它是著名的“电视绿色”或“绿色屏幕”颜色。

挑战

您的任务是编写一个程序,以获取两个输入图像,这些输入图像均为PNG格式(或图像库的图像对象类型),并且尺寸相同。一个图像可以是任何旧图像。另一个是具有颜色背景的图像#00FF00。输出图像将由第二个图像覆盖,第一个图像重叠在第一个图像上,不#00FF00存在任何颜色(第一个图像除外)。输入和输出可以通过文件,一个GUI,等你被允许采取的RGB值的数组作为输入来完成,因为看到这里。您可以假定图像仅具有完全不透明的像素。

基本上...

制作一个程序,将#00FF00一个图像中的每个像素都替换为背景图像中的相应像素。

测试用例

@dzaima慷慨提供的:背景: 前景: 输出:
我的个人资料图片

丹尼斯

输出


当然,严格禁止标准漏洞。这包括使用在线资源为您做到这一点。
这是,所以最短的代码胜利和最好的程序员可能会繁荣...


2
我们可以将语言/库的本机格式的图像对象作为输入,还是必须通过文件名读取图像?
notjagan

@notjagan您可以将图像对象作为输入。
ckjbgames

3
整数数组的I / O是可接受的还是我们实际上局限于其他一组图像I / O?
乔纳森·艾伦

1
@PeterCordes我会允许的。
ckjbgames

1
@PeterCordes ok
ckjbgames

Answers:


14

x86-64(和x86-32)机器代码,13 15 13字节

更新日志:

  1. 错误修正:第一个版本仅检查G = 0xff,而不要求R和B为0。我更改为修改背景,以便可以lodsd在前景上使用fg像素eax进行简短cmp eax, imm32编码(5个字节) ),而不是cmp dh,0xff(3个字节)。

  2. 保存2个字节:注意,使用的内存操作数来修改bg可以cmov节省2个字节的mov负载(如果需要的话,还可以保存一个寄存器)。


这是遵循x86-64 System V调用约定的函数,可以使用以下签名直接从C或C ++(在x86-64非Windows系统上)直接调用:

void chromakey_blend_RGB32(uint32_t *background /*rdi*/,
                     const uint32_t *foreground /*rsi*/,
                  int dummy, size_t pixel_count /*rcx*/);

图像格式为RGB0 32bpp,绿色分量位于每个像素内第二低的内存地址。前景背景图像就地改性。 pixel_count是行*列。它不在乎行/列;它只是chromekey融合了您指定的许多内存双字。

RGBA(要求A为0xFF)将要求使用其他常数,但函数大小不变。将前景DWORD与存储在4个字节中的任意32位常量进行精确相等性比较,因此可以轻松支持任何像素顺序或色键颜色。

相同的机器码也可以在32位模式下工作。要汇编为32位,rdiedi在源中更改为。所有其他变为64位的寄存器都是隐式的(lodsd / stosd和循环),而其他显式寄存器则保持32位。但是请注意,您需要一个包装器才能从32位C进行调用,因为没有一个标准的x86-32调用约定使用与x86-64 SysV相同的regs。

NASM列表(机器代码+源代码),针对asm初学者进行了评论,并提供了对更复杂指令的描述。(在正常使用情况下,复制说明参考手册是不好的方式。)

 1                       ;; inputs:
 2                       ;; Background image pointed to by RDI, RGB0 format  (32bpp)
 3                       ;; Foreground image pointed to by RSI, RGBA or RGBx (32bpp)
 4          machine      ;; Pixel count in RCX
 5          code         global chromakey_blend_RGB32
 6          bytes        chromakey_blend_RGB32:
 7 address               .loop:                      ;do {
 8 00000000 AD               lodsd                   ; eax=[rsi], esi+=4. load fg++
 9 00000001 3D00FF0000       cmp    eax, 0x0000ff00  ; check for chromakey
10 00000006 0F4407           cmove  eax, [rdi]       ; eax = (fg==key) ? bg : fg
11 00000009 AB               stosd                   ; [rdi]=eax, edi+=4. store into bg++
12 0000000A E2F4             loop .loop              ;} while(--rcx)
13                       
14 0000000C C3               ret

##  next byte starts at 0x0D, function length is 0xD = 13 bytes

要从此清单中获取原始NASM来源,请使用去除每行的前26个字符<chromakey.lst cut -b 26- > chromakey.asm。我用
nasm -felf64 chromakey-blend.asm -l /dev/stdout | cut -b -28,$((28+12))- NASM清单生成了此列,因此在机器代码和源代码之间留了比我想要的更多的空白列。要构建目标文件,可以使用C或C ++进行链接nasm -felf64 chromakey.asm。(或yasm -felf64 chromakey.asm)。

未经测试,但我非常有信心load / load / cmov / store的基本思想是合理的,因为它是如此简单。

如果我可以要求调用者将色键常量(0x00ff00)作为额外的arg传递,而不是将常量硬编码到函数中,则可以节省3个字节。我不认为通常的规则允许编写更通用的函数来让调用者为其设置常量。但是,如果这样做的话,第三个arg(当前为dummy)将传递edx到x86-64 SysV ABI中。只需将cmp eax, 0x0000ff00(5B)更改为cmp eax, edx(2B)。


随着SSE4或AVX,你可能会做得更快(但较大的代码大小),与pcmpeqdblendvps做一个32位的元素大小可变共混物的比较掩码进行控制。(使用pand,您可以忽略高字节)。对于压缩的RGB24,您可以使用pcmpeqb,然后使用2x pshufb+ pand来获取该像素的所有3个分量都匹配的字节的TRUE,然后使用pblendvb

(我知道这是代码高尔夫,但我确实考虑过在使用标量整数之前尝试使用MMX。)


您能给我发送用该机器代码制成的可执行文件吗?
ckjbgames

请x86_32。
ckjbgames

@ckjbgames:我还没有编写调用程序来加载/保存图像,只是编写了“ inplace-pixels-in-place”部分。我必须先这样做,然后才能构建可执行文件。但是,如果我这样做了,什么样的可执行文件呢?Windows PE32?Linux ELF32?FreeBSD ??
彼得·科德斯

ELF32,如果可以的话。
ckjbgames

@ckjbgames:如果有时间,我会寻找图像加载库并编写一些东西。我添加了一段有关如何将清单转换成可使用的代码的段落nasm -felf32。(对于32位,您还需要一个包装函数来从C调用,因为它仍使用与x86-64 SysV ABI相同的寄存器。)
Peter Cordes

13

Mathematica 57 35字节

更新:默认情况下,使用删除绿色背景RemoveBackground。第一个提交包含不必要的第二个参数“ {“ Background”,Green}“。


#~ImageCompose~RemoveBackground@#2&

删除图像2的背景,并将结果与​​图像1合成。


i1

以下以前缀而不是中缀的形式更清楚地显示了代码的工作方式。

i2


4
这对于不是绿色的“背景”的图像是否有用?(您的输出中似乎还剩下一小块绿色)
DBS

如果图片中有一个绿色的“岛”,则需要附加参数“ {{Background”,Green}”,这将总数增加到57个字节。这是我的第一次提交,因为我看不到绿色孤立在图片的前景中,该参数已被删除
。– DavidC

11

Python 3 + numpy,59个字节

lambda f,b:copyto(f,b,'no',f==[0,255,0])
from numpy import*

在线尝试!

输入以numpy数组格式给出,整数三元组代表像素(#00FF00十六进制颜色代码等于[0, 255, 0])。输入数组在适当的地方修改,每个meta都允许。

示例图片

输入(来自问题)

背景:

ckjbgames的头像

前景:

Dennis的头像

运行该功能后的前景图像:

#00FF00的合并图像替换为背景像素

参考实现(用于opencv读取图像文件)

g = lambda f,b:copyto(f,b,'no',f==[0,255,0])
from numpy import*

import cv2

f = cv2.imread("fg.png")
b = cv2.imread("bg.png")

g(f, b)

cv2.imshow("Output", f)
cv2.imwrite("out.png", f)

在屏幕上显示图像并将其写入输出文件。


17
结果图像上的所有红点是什么?
Yytsi'7

1
我问过有关I / O的问题-这似乎符合当前的措辞(即“您的库”),如果是,那么cv2本身是否需要numpy导入?如果没有,您可以在54中通过不使用任何numpy函数并且不导入numpy:来实现lambda f,b:[x[list(x[0])==[0,255,0]]for x in zip(f,b)]。如果整数列表的列表实际上也是可接受的,那么您可以使用lambda f,b:[x[x[0]==[0,255,0]]for x in zip(f,b)]
Jonathan Allan

即使numpy的..in其实需要CV2来执行转换我仍然认为你可以做的54字节的版本,因为我们并不需要进口CV2的挑战。
乔纳森·艾伦

5
如果为 G == 255,即使R和B不为零,该值也会被替换,这会导致出现红点。对于其他乐队来说,即使是很难看到的,也会发生这种情况。因此,即使仅满足一个条件,它也可以彼此独立地执行逻辑检查并交换出单个通道。例如,如果一个像素是[0 255 37]红色和绿色带,将被替换。
Leander Moesinger

2
@LeanderMoesinger:发现得很好。我也有这个bug>。<; IDK为什么我认为在忽略R和B的同时仅检查green = 0xFF是正确的!
彼得·科德斯

9

处理中,116 99字节

PImage f(PImage b,PImage f){int i=0;for(int c:f.pixels){if(c!=#00FF00)b.pixels[i]=c;i++;}return b;}

不幸的是,处理不支持Java 8,例如lambdas。

示例实现:(将图像另存为out.png并在屏幕上绘制)

PImage bg;
void settings() {
  bg = loadImage("bg.png");
  size(bg.width,bg.height);
}
void setup() {
  image(f(bg, loadImage("fg.png")), 0, 0);
  save("out.png");
}
PImage f(PImage b,PImage f){int i=0;for(int c:f.pixels){if(c!=#00FF00)b.pixels[i]=c;i++;}return b;}

您可以摆脱settings()setup()函数,而直接运行代码。
凯文·沃克曼

@KevinWorkman我在那里进行设置和设置,以便将其显示在屏幕上,否则将无法显示
dzaima

#ff000xff00相同#00ff00的处理?
彼得·科德斯

不幸的是,@ PeterCordes#FF00给出语法错误,并且#00FF00 == 0xFF00FF00,所以0xFF00不起作用,因为它检查alpha值0
dzaima

@dzaima:可以以RGB0格式拍摄图像0x0000FF00吗,您要寻找的位模式是吗?
彼得·科德斯

6

Bash + ImageMagick,45个字节

convert $1 $2 -transparent lime -composite x:

将两个图像作为参数并在屏幕上显示输出。更改x:$3写入第三个文件参数。方法很简单:读取“背景”图像;阅读“前景”图像;重新将颜色“石灰”(#00ff00)解释为第二个图像中的透明度;然后将第二个图像合成到第一个图像上并输出。

ImageMagick:28个字节?

我本可以作为ImageMagick的答案提交的,但尚不清楚如何处理参数。如果您想假设ImageMagick是一种基于堆栈的语言(这有点不正确,但是几乎...这很奇怪),那么-transparent lime -composite该函数可以在堆栈上保留两个图像,并在堆栈上保留一个合并的图像。也许这足以计算?


3

MATL40 37 31字节

,jYio255/]tFTF1&!-&3a*5M~b*+3YG

使用脱机解释器运行的示例。图像通过其URL输入(也可以提供本地文件名)。

在此处输入图片说明

说明

,        % Do this twice
  j      %   Input string with URL or filename
  Yi     %   Read image as an M×N×3 uint8 array
  o      %  Convert to double
  255/   %   Divide by 255
]        % End
t        % Duplicate the second image
FTF      % Push 1×3 vector [0 1 0]
1&!      % Permute dimensions to give a 1×1×3 vector
-        % Subtract from the second image (M×N×3 array), with broadcast
&3a      % "Any" along 3rd dim. This gives a M×N mask that contains
         % 0 for pure green and 1 for other colours
*        % Mulltiply. This sets green pixels to zero
5M       % Push mask M×N again
~        % Negate
b        % Bubble up the first image
*        % Multiply. This sets non-green pixels to zero
+        % Add the two images
3YG      % Show image in a window

3

Pyth,27个字节

M?q(Z255Z)GHG.wmgVhded,V'E'

它需要带引号的输入。输入是图像文件的两个路径。输出文件o.png不幸的是,出于安全原因,该文件无法在在线解释器上进行测试('已被禁用)。您需要在计算机上安装Pyth才能对其进行测试。

说明

M?q(Z255Z)GHG                  # Define a function g which takes two tuples G and H and returns G if G != (0, 255, 0), H otherwise
                       V'E'    # Read the images. They are returned as lists of lists of colour tuples
                      ,        # Zip both images
               m  hded         # For each couple of lists in the zipped list...
                gV             # Zip the lists using the function g
             .w                # Write the resulting image to o.png

色键混合功能本身是13个字节,与我的x86机器代码答案相同。我之前没有意识到这也是处理图像I / O的完整程序。
彼得·科德斯

2

Matlab 2016b和Octave,62 59字节

输入:A = MxNx3 unit8前景矩阵,B = MxNx3 unit8背景矩阵。

k=sum(A(:,:,2)-A(:,:,[1 3]),3)==510.*ones(1,1,3);A(k)=B(k);

输出:A = MxNx3 unit8矩阵

样品使用:

A = imread('foreground.png');
B = imread('backgroundimg.png');

k=sum(A(:,:,2)-A(:,:,[1 3]),3)==510.*ones(1,1,3);A(k)=B(k);

imshow(A)

1

C ++,339个字节

这使用CImg,它也可以采用其他格式的文件。结果显示在一个窗口中。

#include<CImg.h>
using namespace cimg_library;
int main(int g,char** v){CImg<unsigned char> f(v[1]),b(v[2]);for(int c=0;c<f.width();c++){for(int r=0;r<f.height();r++){if((f(c,r)==0)&&(f(c,r,0,1)==255)&&(f(c,r,0,2)==0)){f(c,r)=b(c,r);f(c,r,0,1)=b(c,r,0,1);f(c,r,0,2) = b(c,r,0,2);}}}CImgDisplay dis(f);while(!dis.is_closed()){dis.wait();}}

用编译g++ chromakey.cpp -g -L/usr/lib/i386-linux-gnu -lX11 -o chromakey -pthread


1

R,135个字节

function(x,y,r=png::readPNG){a=r(x);m=apply(a,1:2,function(x)all(x==0:1));for(i in 1:4)a[,,i][m]=r(y)[,,i][m];png::writePNG(a,"a.png")}

匿名函数,将2个png文件路径作为参数,并输出称为的png图片a.png

略有偏离,并附有解释:

function(x,y){
    library(png)
    # readPNG output a 3D array corresponding to RGBA values on a [0,1] scale:
    a = readPNG(x)
    # Logical mask, telling which pixel is equal to c(0, 1, 0, 1), 
    # i.e. #00FF00 with an alpha of 1:
    m = apply(a, 1:2, function(x) all(x==0:1))
    # For each RGB layer, replace that part with the equivalent part of 2nd png:
    for(i in 1:4) a[,,i][m] = readPNG(y)[,,i][m]
    writePNG(a,"a.png")
}

1

SmileBASIC,90字节是什么关键

DEF C I,J
DIM T[LEN(I)]ARYOP.,T,I,16711936ARYOP 2,T,T,T
ARYOP 6,T,T,0,1ARYOP 5,I,I,J,T
END

I是前景,输出,J是背景。两者都是像素的整数阵列,采用32位ARGB格式。

不打高尔夫球

DEF C IMAGE,BACKGROUND 'function
 DIM TEMP[LEN(IMAGE)]  'create array "temp"
 ARYOP #AOPADD,TEMP,IMAGE,-RGB(0,255,0)    'temp = image - RGB(0,255,0)
 ARYOP #AOPCLP,TEMP,TEMP,-1,1              'temp = clamp(temp, -1, 1)
 ARYOP #AOPMUL,TEMP,TEMP,TEMP              'temp = temp * temp
 ARYOP #AOPLIP,IMAGE,IMAGE,BACKGROUND,TEMP 'image = linear_interpolate(image, background, temp)
END

说明:

ARYOP是将简单操作应用于数组中每个元素的函数。
叫做ARYOP mode, output_array, input_array_1, input_array_2, ...

首先,要确定图像中的哪些像素是绿色,请-16711936从前景图像中的每个像素中减去(绿色的RGBA表示)。这给出了一个数组,其中0代表绿色像素,任何其他数字代表非绿色像素。

要将所有非零值转换为1,将它们平方(以除去负数),然后将其钳位在0和之间1

结果是只有0s和1s 的数组。
0s代表前景图像中的绿色像素,应替换为来自背景的像素。
1s代表非绿色像素,需要将其替换为前景中的像素。

使用线性插值可以很容易地做到这一点。


0

PHP,187字节

for($y=imagesy($a=($p=imagecreatefrompng)($argv[1]))-1,$b=$p($argv[2]);$x<imagesx($a)?:$y--+$x=0;$x++)($t=imagecolorat)($b,$x,$y)-65280?:imagesetpixel($b,$x,$y,$t($a,$x,$y));imagepng($b);

假设使用24位PNG文件;从命令行参数获取文件名,然后写入stdout。
用运行-r

分解

for($y=imagesy(                                 # 2. set $y to image height-1
        $a=($p=imagecreatefrompng)($argv[1])    # 1. import first image to $a
    )-1,
    $b=$p($argv[2]);                            # 3. import second image to $b
    $x<imagesx($a)?:                            # Loop 1: $x from 0 to width-1
        $y--+$x=0;                              # Loop 2: $y from height-1 to 0
        $x++)
            ($t=imagecolorat)($b,$x,$y)-65280?:     # if color in $b is #00ff00
                imagesetpixel($b,$x,$y,$t($a,$x,$y));   # then copy pixel from $a to $b
imagepng($b);                                   # 5. output

0

JavaScript(ES6),290字节

a=>b=>(c=document.createElement`canvas`,w=c.width=a.width,h=c.height=a.height,x=c.getContext`2d`,x.drawImage(a,0,0),d=x.getImageData(0,0,w,h),o=d.data,o.map((_,i)=>i%4?0:o[i+3]=o[i++]|o[i++]<255|o[i]?255:0),x.drawImage(b,0,0),createImageBitmap(d).then(m=>x.drawImage(m,0,0)||c.toDataURL()))

将输入作为两个Image对象(使用递归语法),可以使用HTML <image>元素创建。返回一个Promise,它解析为结果图像的Base64数据URL,该URL可应用于src<image>

此处的想法是将每个#00FF00像素的alpha值设置为0,然后在背景上方画出前景,并抠出背景。

测试片段

由于他们的数据URL包含前景和背景太大,因此无法在此处发布,因此将其移至CodePen:

在线尝试!


0

OSL,83字节

shader a(color a=0,color b=0,output color c=0){if(a==color(0,1,0)){c=b;}else{c=a;}}

接受两个输入。第一个是前景,第二个是背景。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.