如何从彩色图像创建CMYK半色调图像?

12
我正在进行一个项目,需要将CYMK图像中的每个颜色分离出来,并生成一张半色调图片,以便在特殊的半色调打印机上打印。所使用的方法类似于丝网印刷,流程几乎相同。先拍照,然后分离出每个颜色通道。接着为半色调制作一个屏幕。每个颜色屏幕必须按15-45度(可调)倾斜。点大小和LPI必须根据用户的可配置值计算,以实现不同的效果。我被告知这种过程用于丝网印刷,但我无法找到任何解释CYMK半色调的信息。我发现很多有关于将图像降为单色并生成新的黑白半色调图像的资料。
我猜想我需要:
  1. 将文件分成它的颜色通道。
  2. 为该通道生成单色半色调图像。
  3. 将生成的半色调图像按照通道数目 * 角度数量 倾斜。
请问是否有人知道这是否是正确的方法,是否存在针对此过程或算法的任何Python代码或任何好的解释呢?

1
已更新了我的代码,希望有所帮助 ;) - fraxel
3个回答

32

我曾经经营过一个丝网印刷工作室(规模相当小),虽然我实际上从未进行过色彩分离印刷,但对原理有一定的了解。这是我的处理方法:

  1. 将图像分成C、M、Y、K四个通道。
  2. 将每个分隔出来的图像分别旋转0度、15度、30度和45度。
  3. 对每个图像进行半色调处理(点大小将与强度成比例)。
  4. 将每个半色调图像旋转回去。

现在你就得到了你的分色图像。正如你所提到的,旋转步骤可以减少点对齐问题(否则会搞乱一切),而诸如Moiré pattern effects之类的问题也会被合理地减少。

使用PIL编写代码非常容易。

更新2:

我写了一些快速的代码,可以为您完成这项任务,它还包括一个 GCR 函数(下面详细说明):

import Image, ImageDraw, ImageStat

def gcr(im, percentage):
    '''basic "Gray Component Replacement" function. Returns a CMYK image with 
       percentage gray component removed from the CMY channels and put in the
       K channel, ie. for percentage=100, (41, 100, 255, 0) >> (0, 59, 214, 41)'''
    cmyk_im = im.convert('CMYK')
    if not percentage:
        return cmyk_im
    cmyk_im = cmyk_im.split()
    cmyk = []
    for i in xrange(4):
        cmyk.append(cmyk_im[i].load())
    for x in xrange(im.size[0]):
        for y in xrange(im.size[1]):
            gray = min(cmyk[0][x,y], cmyk[1][x,y], cmyk[2][x,y]) * percentage / 100
            for i in xrange(3):
                cmyk[i][x,y] = cmyk[i][x,y] - gray
            cmyk[3][x,y] = gray
    return Image.merge('CMYK', cmyk_im)

def halftone(im, cmyk, sample, scale):
    '''Returns list of half-tone images for cmyk image. sample (pixels), 
       determines the sample box size from the original image. The maximum 
       output dot diameter is given by sample * scale (which is also the number 
       of possible dot sizes). So sample=1 will presevere the original image 
       resolution, but scale must be >1 to allow variation in dot size.'''
    cmyk = cmyk.split()
    dots = []
    angle = 0
    for channel in cmyk:
        channel = channel.rotate(angle, expand=1)
        size = channel.size[0]*scale, channel.size[1]*scale
        half_tone = Image.new('L', size)
        draw = ImageDraw.Draw(half_tone)
        for x in xrange(0, channel.size[0], sample):
            for y in xrange(0, channel.size[1], sample):
                box = channel.crop((x, y, x + sample, y + sample))
                stat = ImageStat.Stat(box)
                diameter = (stat.mean[0] / 255)**0.5
                edge = 0.5*(1-diameter)
                x_pos, y_pos = (x+edge)*scale, (y+edge)*scale
                box_edge = sample*diameter*scale
                draw.ellipse((x_pos, y_pos, x_pos + box_edge, y_pos + box_edge), fill=255)
        half_tone = half_tone.rotate(-angle, expand=1)
        width_half, height_half = half_tone.size
        xx=(width_half-im.size[0]*scale) / 2
        yy=(height_half-im.size[1]*scale) / 2
        half_tone = half_tone.crop((xx, yy, xx + im.size[0]*scale, yy + im.size[1]*scale))
        dots.append(half_tone)
        angle += 15
    return dots

im = Image.open("1_tree.jpg")

cmyk = gcr(im, 0)
dots = halftone(im, cmyk, 10, 1)
im.show()
new = Image.merge('CMYK', dots)
new.show()

这将会将这个:

enter image description here

转变成这个(眯起眼睛并离开监视器):

enter image description here

请注意,图像采样可以是逐像素的(因此在最终图像中保留原始图像的分辨率)。通过设置sample = 1来实现这一点,在这种情况下,您需要将scale设置为一个更大的数字,以便有多个可能的点大小。这也会导致更大的输出图像大小(原始图像大小* scale ** 2,所以要小心!)。

默认情况下,从RGB转换为CMYK时,K通道(黑色通道)为空。是否需要K通道取决于您的印刷过程。您可能希望这样做的原因有很多:获得比 CMY 重叠更好的黑色,节省墨水,改善干燥时间,减少墨水渗出等。无论如何,我还编写了一个小的GCR(灰色组件替换)函数GCR,因此您可以设置要替换的CMY重叠的K通道的百分比(我在代码注释中进一步解释了这一点)。

下面是几个示例来说明。使用sample = 1scale = 8 对图像中的letter F进行处理,因此分辨率相当高。

4个CMYK通道,其中percentage = 0,因此为空的K通道:

enter image description hereenter image description hereenter image description hereenter image description here

合并成:

enter image description here

CMYK通道,其中percentage = 100,因此使用了K通道。您可以看到青色通道完全被压制,洋红色和黄色通道在图像底部的黑色带中使用了更少的墨水:

enter image description hereenter image description hereenter image description hereenter image description hereenter image description here


我尝试了一些从网上找到的角度,范围从每个通道的10度到60度不等,并发现某些组合会导致非常严重的moire图案效果,而其他组合则可以减少这些效果。我的代码允许我为每个通道设置特定的角度,因此我可以使用非增量角度。这似乎有助于减少效果,但也可能改变感知颜色。这很酷,可以随意尝试。;-) - user693336
抱歉名字混乱。我仍然不确定这个网站是如何工作的。 - user693336
哈哈。我刚刚试图删除我的回答,以防有所帮助,但一旦被标记为最佳答案,我就无法删除它了... - andrew cooke
@andrew cook - 哈,干杯 ;) 希望user69333能够解决问题。总之,我玩这些东西很开心,这才是最重要的! - fraxel
@fraxel - 好的,我希望在这个帖子上点击对勾会接受你的答案?我刚刚花了几分钟时间尝试了一下你的代码。很酷的东西。谢谢! - user693336
显示剩余5条评论

6

我的解决方案也使用了PIL,但依赖于内部支持的抖动方法(Floyd-Steinberg)。然而,这样会产生伪像,因此我正在考虑重写其C代码。

    from PIL import Image

    im  = Image.open('tree.jpg')             # open RGB image
    cmyk= im.convert('CMYK').split()         # RGB contone RGB to CMYK contone
    c = cmyk[0].convert('1').convert('L')    # and then halftone ('1') each plane
    m = cmyk[1].convert('1').convert('L')    # ...and back to ('L') mode
    y = cmyk[2].convert('1').convert('L')
    k = cmyk[3].convert('1').convert('L')

    new_cmyk = Image.merge('CMYK',[c,m,y,k]) # put together all 4 planes
    new_cmyk.save('tree-cmyk.jpg')           # and save to file

隐式的GCR PIL应用也可以扩展为更通用的方法,但我试图描述一个简单的解决方案,其中分辨率和采样也被忽略。

1

使用有序(Bayer)Dither和D4模式,我们可以修改@fraxel的代码,最终得到一个输出抖动图像,其高度和宽度是输入彩色图像的4倍,如下所示

import matplotlib.pylab as plt

def ordered_dither(im, D4):
    im = (15 * (im / im.max())).astype(np.uint8)
    h, w = im.shape
    im_out = np.zeros((4 * h, 4 * w), dtype = np.uint8)
    x, y = 0, 0
    for i in range(h):
        for j in range(w):
            im_out[x:x + 4, y:y + 4] = 255 * (D4 < im[i, j])
            y = (y + 4) % (4 * w)
        x = (x + 4) % (4 * h)
    return im_out

def color_halftoning_Bayer(cmyk, angles, D4):
    out_channels = []    
    for i in range(4):
        out_channel = Image.fromarray(
                            ordered_dither(np.asarray(cmyk[i].rotate(angles[i],expand=1)), D4)
                      ).rotate(-angles[i], expand=1)
        x = (out_channel.size[0] - cmyk[i].size[0]*4) // 2
        y = (out_channel.size[1] - cmyk[i].size[1]*4) // 2
        out_channel = out_channel.crop((x, y, x + cmyk[i].size[0]*4, y + cmyk[i].size[1]*4))
        out_channels.append(out_channel)
    return Image.merge('CMYK',out_channels)

image = Image.open('images/tree.jpg')
cmyk = gcr(image,100).split()   
D4 = np.array([[ 0,  8,  2, 10],
               [12,  4, 14,  6],
               [ 3, 11,  1,  9],
               [15,  7, 13,  5]], dtype=np.uint8)

out = np.asarray(color_halftoning_Bayer(cmyk, np.linspace(15, 60, 4), D4).convert('RGB'))
plt.figure(figsize=(20,20))
plt.imshow(out)
plt.show()

当在以下树形输入上运行上述代码时:

enter image description here

它生成以下抖动输出:

enter image description here


gcr()函数是做什么的?我不明白它来自哪里。 - Prof. Falken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接