目录

1 问题描述

2 技术路线

2.1 获取原图灰度值

2.2 抖动

2.3 用抖动生成的灰度填充图像

4 实现思路

4.1 原画像素2*2,新画像素2*2,4级灰度

4.2 原画像素1*1,新画像素2*2,4级灰度

4.3 原画像素1*1,新画像素4*4,16级灰度

5 实验结果

致谢

附录A

附录B

附录C


1 问题描述

受童晶老师文章的启发,其实现了图像像素采样的绘制。

童晶老师文章链接:第13章 图像像素采样(《Python趣味创意编程》教学视频)

现在用“抖动”的方法画像素画。并且完成三种像素画。

1)原画像素2*2,新画像素2*2,4级灰度,即原画像素与新画像素个数比为1:1。

2)原画像素1*1,新画像素2*2,4级灰度,即原画像素与新画像素个数比为1:4。

3)原画像素1*1,新画像素4*4,16级灰度,即原画像素与新画像素个数比为1:16。

2 技术路线

2.1 获取原图灰度值

灰度图,又称为灰阶图。任何颜色都是由红、绿、蓝三原色组成,而灰度图只有一个通道,灰度图有256个灰度等级。灰度图的灰度值范围是0~255,灰度值为0代表全黑,灰度值为255代表全白。可以使用画图的颜色编辑功能,将红绿蓝三个通道的数值设定为相同值就可以看到其对应的灰度效果。

如图2-1,这是“lena”图像的灰度图。该图像大小为512*512,有512*512个像素,我们可以获取灰度图像中每一个像素的灰度值,并且根据灰度值去做接下来的工作。

获取原图灰度值方法:

使用openCV库中的函数img[y, x],可以获得图像中指定位置(x,y)的灰度值。

android 像素抖动原理 像素抖动教程_灰度

图2-1 

2.2 抖动

图像抖动(Dithering)是像素画创作中的一种常见方法,它是是通过混合2种不同颜色的像素方块,获得更多颜色的技巧。

图像抖动技术通常应用于打印机、显示器和图形处理器等领域。在这些应用中,由于资源的限制,通常需要在有限的颜色深度下处理图像。图像抖动技术可以用较少的灰度实现多种灰度的效果。

具体如何抖动?例如,现在需要用2种颜色,即黑色(灰度值为0)和白色(灰度值为255)实现4种灰度级的颜色,即2色实现4色抖动。具体做法如下。

定义4种2*2的像素矩阵(代表了用2色实现的4种灰度),4种灰度分别为:

a.如图2-2 灰度a,在矩阵(0,0)位置灰度值为0,(0,1)、(1,0)、(1,1)位置灰度值为255,这是灰度a的一种情况,即2*2像素矩阵种填充1个黑色块代表灰度a。

android 像素抖动原理 像素抖动教程_图像处理_02

图2-2 灰度a 

b.如图2-3 灰度b,在矩阵(0,0)和(1,1)位置灰度值为0,(1,0)和(0,1)位置灰度值为255,这是灰度b的一种情况,即2*2像素矩阵种填充2个黑色块代表灰度b。

android 像素抖动原理 像素抖动教程_opencv_03

图2-3 灰度b

c. 如图2-4 灰度c,在矩阵(0,0)、(1,0)和(1,1)位置灰度值为0,(0,1)位置灰度值为255,这是灰度c的一种情况,即2*2像素矩阵种填充3个黑色块代表灰度c。

android 像素抖动原理 像素抖动教程_灰度_04

图2-4 灰度c

 d. 如图1-4 灰度d,在矩阵(0,0)、(1,0)、(0,1)和(1,1)位置灰度值为0,即2*2像素矩阵种填充4个黑色块代表灰度d。

android 像素抖动原理 像素抖动教程_android 像素抖动原理_05

图2-5 灰度d 

如上所述在生成每一级灰度时,用随机填充黑色块的方式。

在一个像素矩阵中,一种灰度级随机填充黑色块的原因:

例如,一个2*2的像素矩阵,现在要生成一种灰度,这种灰度在2*2像素矩阵中有两个黑色块和两个白色块。

这种灰度会包含以下这6情况。

android 像素抖动原理 像素抖动教程_图像处理_06

 

android 像素抖动原理 像素抖动教程_计算机视觉_07

 

android 像素抖动原理 像素抖动教程_图像处理_08

android 像素抖动原理 像素抖动教程_android 像素抖动原理_09

 

android 像素抖动原理 像素抖动教程_图像处理_10

 

android 像素抖动原理 像素抖动教程_android 像素抖动原理_11

如果在用生成的灰度填充图像时,固定使用这6种情况的1种,即

android 像素抖动原理 像素抖动教程_计算机视觉_12

如果用这种方法,当图像的某个区域需要填充这种灰度时,可能出现这种情况,如图2-6,会产生摩尔条纹(摩尔条纹是两条线或两个物体之间以恒定的角度和频率发生干涉的视觉结果,当人眼无法分辨这两条线或两个物体时,只能看到干涉的花纹,这种光学现象就是摩尔条纹。)。

android 像素抖动原理 像素抖动教程_计算机视觉_13

图2-6

       现在如果每一级灰度采用随机填充黑色块的方式,那么当图像中的某个区域需要填充这种灰度时是这样的,如图2-7,会减少摩尔条纹。

android 像素抖动原理 像素抖动教程_计算机视觉_14

图2-7

实现一级灰度随机填充黑色块的具体方法如下。

在生成每一级灰度之前,随机获取黑色块在当前在像素矩阵中的坐标。

例如,2*2像素矩阵生成4级灰度中的灰度a(2*2像素矩阵中有一个黑色块)。2*2像素矩阵中有四个位置可以填充黑色块,即四个位置坐标(0,0)(0,1)(1,0)(1,1),通过生成随机数来随机获取这四个坐标中的一个。该功能封装在函数中,函数的参数number代表需要生成的黑色块的个数。函数代码如下。

def get_randomList(number):
    global listAll
    listAll = []
    k = 0
    preLen = 0
    while k < number:
        f = True
        locationList = []
        locationX = random.randint(0, 1)
        locationY = random.randint(0, 1)
        locationList.append(locationX)
        locationList.append(locationY)
        for ele in listAll:
            if ele[0] == locationList[0] and ele[1] == locationList[1]:
                f = False
                break
        if f:
            listAll.append(locationList)
        if preLen == len(listAll):
            continue
        else:
            preLen = len(listAll)
        if len(listAll) == number:
            break
        k = k + 1

对于用2色16像素实现16色抖动,同理。

定义16种4*4的矩阵(16种矩阵代表16种灰度)。并且生成一级灰度时,同样采用随机填充黑色块的方式(原因同上)。

16种灰度A~P如下(每一种灰度展示仅展示一种情况)。

android 像素抖动原理 像素抖动教程_灰度_15

 

android 像素抖动原理 像素抖动教程_计算机视觉_16

 

android 像素抖动原理 像素抖动教程_图像处理_17

 

android 像素抖动原理 像素抖动教程_opencv_18

A                                B                                C                                 D

android 像素抖动原理 像素抖动教程_计算机视觉_19

 

android 像素抖动原理 像素抖动教程_灰度_20

 

android 像素抖动原理 像素抖动教程_android 像素抖动原理_21

 

android 像素抖动原理 像素抖动教程_计算机视觉_22

             E                                F                              G                                    H                

android 像素抖动原理 像素抖动教程_灰度_23

 

android 像素抖动原理 像素抖动教程_opencv_24

 

android 像素抖动原理 像素抖动教程_灰度_25

 

android 像素抖动原理 像素抖动教程_计算机视觉_26

I                                  J                                    K                               L

android 像素抖动原理 像素抖动教程_android 像素抖动原理_27

 

android 像素抖动原理 像素抖动教程_计算机视觉_28

 

android 像素抖动原理 像素抖动教程_计算机视觉_29

 

android 像素抖动原理 像素抖动教程_android 像素抖动原理_30

M                                   N                                   O                                P

2.3 用抖动生成的灰度填充图像

对于4级灰度,我们将灰度值区间[0,255]等分为4个区间,即[0,64)、[64,128)、[128,192)和[192,255]。

填充图像过程中,

当图像中像素灰度值在区间[0,64),填充灰度d,

当图像中像素灰度值在区间[64,128),填充灰度c,

当图像中像素灰度值在区间[128,192),填充灰度b,

当图像中像素灰度值在区间[192,255],填充灰度a。

对于16级灰度,我们将灰度值区间[0,255]等分为16个区间,即[0,16)、[16,32)、[32,48)、[48,64)、[64,80)、[80,96)、[96,112)、[112,128)、[128,144)、[144,160)、[160,176)、[176,192)、[192,208)、[208,224)、[224,240)、[240,255]。

填充图像过程中,

当图像中像素灰度值在区间[0,16),填充灰度P,

当图像中像素灰度值在区间[16,32),填充灰度O,

当图像中像素灰度值在区间[32,48),填充灰度N,

……

当图像中像素灰度值在区间[224,240),填充灰度B,

当图像中像素灰度值在区间[240,255],填充灰度A。

4 实现思路

对于需要的三种图像,实现过程分别如下。

4.1 原画像素2*2,新画像素2*2,4级灰度

原画像素2*2,新画像素2*2,4级灰度,即原画像素与新画像素个数比为1:1。

1.读入“lena”图像,并转图像为灰度图。代码片段如下。

img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)

2.像素宽度设置为2,获取灰度图的中像素灰度值,保存在列表hdzAll中。代码片段如下。

while x < xStop:
    y = 0
    while y < yStop:
        hdz.append(img[y, x])
        y = y + step
    hdzAll.append(hdz)
    hdz = []
    x = x + step

3.根据获取的像素灰度值,用抖动生成的灰度a~d重新填充图像。

填充图像过程中,

当图像中像素灰度值在区间[0,64),填充灰度d,

当图像中像素灰度值在区间[64,128),填充灰度c,

当图像中像素灰度值在区间[128,192),填充灰度b,

当图像中像素灰度值在区间[192,255],填充灰度a。

完整代码见附录A。

4.2 原画像素1*1,新画像素2*2,4级灰度

原画像素1*1,新画像素2*2,4级灰度,即原画像素与新画像素个数比为1:4。

1.读入“lena”图像,并转图像为灰度图。

2.将原图a的长宽都增大为原来的2倍,得到图b,即图b的像素个数为原图a的4倍。

3.像素宽度设置为2,获取灰度图的中像素灰度值,保存在列表hdzAll中。

4.根据获取的像素灰度值,用抖动生成的灰度a~d重新填充图像。填充方法同上。

完整代码见附录B。

4.3 原画像素1*1,新画像素4*4,16级灰度

原画像素1*1,新画像素4*4,16级灰度,即原画像素与新画像素个数比为1:16。

1.读入“lena”图像,并转图像为灰度图。

2.将原图a的长宽都增大为原来的4倍,得到图b,即图b的像素个数为原图a的16倍。

3.像素宽度设置为4,获取灰度图的中像素灰度值,保存在列表hdzAll中。

4.根据获取的像素灰度值,用抖动生成的灰度A~P重新填充图像。

填充图像过程中,

当图像中像素灰度值在区间[0,16),填充灰度P,

当图像中像素灰度值在区间[16,32),填充灰度O,

当图像中像素灰度值在区间[32,48),填充灰度N,

……

当图像中像素灰度值在区间[224,240),填充灰度B,

当图像中像素灰度值在区间[240,255],填充灰度A。

完整代码见附录C。

5 实验结果

以下为生成的三种像素图,即1倍像素、4倍像素以及16倍像素,随着像素的增多,通过抖动实现的像素画的效果也更接近原画。如图5-1,为原画。

android 像素抖动原理 像素抖动教程_灰度

图5-1 

与原图像素比为1:1,4级灰度,新图效果如图5-2。

android 像素抖动原理 像素抖动教程_opencv_32

 图5-2

与原图像素比为4:1,4级灰度,新图效果如图5-3。

android 像素抖动原理 像素抖动教程_图像处理_33

图5-3

与原图像素比为16:1,16级灰度,新图效果如图5-4。

android 像素抖动原理 像素抖动教程_灰度_34

图5-4 

致谢

感谢童晶老师的启发,以及感谢我的导师杨贵福老师在我学习过程中对我的指导。

附录A 

实现像素画,与原画像素比为1:1,完整代码如下。

import cv2
import numpy as np
import imutils
import random
img = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)
listAll = []
# 随机产生黑色像素块的坐标
def get_randomList(number):
    global listAll
    listAll = []
    # number 的代表的数字组合 这个组合代表了产生黑色像素块的位置
    k = 0
    preLen = 0
    while k < number:
        f = True
        locationList = []
        locationX = random.randint(0, 1)
        locationY = random.randint(0, 1)
        locationList.append(locationX)
        locationList.append(locationY)
        for ele in listAll:
            if ele[0] == locationList[0] and ele[1] == locationList[1]:
                f = False
                break
        if f:
            listAll.append(locationList)
        if preLen == len(listAll):
            continue
        else:
            preLen = len(listAll)
        if len(listAll) == number:
            break
        k = k + 1
hw = img.shape
print(hw)
x = 0
y = 0
step = 2
xStop = hw[1]
yStop = hw[0]
# print(img.shape) 输出图片长和宽
# print(img[500, 500]) 输出灰度值
hdzAll = []
hdz = []
while x < xStop:
    y = 0
    while y < yStop:
        hdz.append(img[y, x])
        y = y + step
    hdzAll.append(hdz)
    hdz = []
    x = x + step
x = 0
y = 0
i = 0
j = 0
while x < xStop:
    y = 0
    j = 0
    while y < yStop:
        c = hdzAll[i][j]
        if 0 <= c < 64:
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度d
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    cv2.fillPoly(img, [np.array(points)], 0)
        elif 64 <= c < 128:
            get_randomList(3)
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度c
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    flag = False
                    for el in listAll:
                        if m == el[0] and n == el[1]:
                            flag = True
                            break
                    if flag:
                        cv2.fillPoly(img, [np.array(points)], 0)
                    else:
                        cv2.fillPoly(img, [np.array(points)], 255)
        elif 128 <= c < 192:
            get_randomList(2)
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度b
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    flag = False
                    for el in listAll:
                        if m == el[0] and n == el[1]:
                            flag = True
                            break
                    if flag:
                        cv2.fillPoly(img, [np.array(points)], 0)
                    else:
                        cv2.fillPoly(img, [np.array(points)], 255)
        elif 192 <= c <= 255:
            get_randomList(1)
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度a
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    flag = False
                    for el in listAll:
                        if m == el[0] and n == el[1]:
                            flag = True
                            break
                    if flag:
                        cv2.fillPoly(img, [np.array(points)], 0)
                    else:
                        cv2.fillPoly(img, [np.array(points)], 255)
        y = y + step
        j = j + 1
    x = x + step
    i = i + 1
cv2.imshow('img', imutils.resize(img, width=400, height=400))
cv2.imwrite('a.jpg', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

附录B

实现像素画,与原画像素比为4:1,完整代码如下。

import cv2
import numpy as np
import imutils
import random
imgOr = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)
h, w = imgOr.shape[:2]
fx = fy = 2
img = cv2.resize(imgOr, None, fx=fx, fy=fy, interpolation=cv2.IMREAD_GRAYSCALE)
listAll = []
def get_randomList(number):
    global listAll
    listAll = []
    # number 的代表的数字组合 这个组合代表了产生黑色像素块的位置
    k = 0
    preLen = 0
    while k < number:
        f = True
        locationList = []
        locationX = random.randint(0, 1)
        locationY = random.randint(0, 1)
        locationList.append(locationX)
        locationList.append(locationY)
        for ele in listAll:
            if ele[0] == locationList[0] and ele[1] == locationList[1]:
                f = False
                break
        if f:
            listAll.append(locationList)
        if preLen == len(listAll):
            continue
        else:
            preLen = len(listAll)
        if len(listAll) == number:
            break
        k = k + 1
hw = img.shape
print(hw)
x = 0
y = 0
step = 2
xStop = hw[1]
yStop = hw[0]
hdzAll = []
hdz = []
while x < xStop:
    y = 0
    while y < yStop:
        hdz.append(img[y, x])
        y = y + step
    hdzAll.append(hdz)
    hdz = []
    x = x + step
x = 0
y = 0
i = 0
j = 0
while x < xStop:
    y = 0
    j = 0
    while y < yStop:
        c = hdzAll[i][j]
        if 0 <= c < 64:
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度d
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    cv2.fillPoly(img, [np.array(points)], 0)
        elif 64 <= c < 128:
            get_randomList(3)
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度c
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    flag = False
                    for el in listAll:
                        if m == el[0] and n == el[1]:
                            flag = True
                            break
                    if flag:
                        cv2.fillPoly(img, [np.array(points)], 0)
                    else:
                        cv2.fillPoly(img, [np.array(points)], 255)
        elif 128 <= c < 192:
            get_randomList(2)
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度b
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    flag = False
                    for el in listAll:
                        if m == el[0] and n == el[1]:
                            flag = True
                            break
                    if flag:
                        cv2.fillPoly(img, [np.array(points)], 0)
                    else:
                        cv2.fillPoly(img, [np.array(points)], 255)
        elif 192 <= c <= 255:
            get_randomList(1)
            for m in range(0, 2):
                for n in range(0, 2):
                    # 填充灰度a
                    points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
                    flag = False
                    for el in listAll:
                        if m == el[0] and n == el[1]:
                            flag = True
                            break
                    if flag:
                        cv2.fillPoly(img, [np.array(points)], 0)
                    else:
                        cv2.fillPoly(img, [np.array(points)], 255)
        y = y + step
        j = j + 1
    x = x + step
    i = i + 1
cv2.imwrite('b.jpg', img)
cv2.imshow('img', imutils.resize(img, width=400, height=400))
cv2.waitKey(0)
cv2.destroyAllWindows()

附录C

实现像素画,与原画像素比为16:1,完整代码如下。

import cv2
import numpy as np
import imutils
import random

imgOr = cv2.imread('img.png', cv2.IMREAD_GRAYSCALE)
h, w = imgOr.shape[:2]
fx = fy = 4
img = cv2.resize(imgOr, None, fx=fx, fy=fy, interpolation=cv2.IMREAD_GRAYSCALE)

listAll = []
# 随机产生黑色像素块的坐标
def get_randomList(number):
    global listAll
    listAll = []
    # number 的代表的数字组合 这个组合代表了产生黑色像素块的位置
    k = 0
    preLen = 0
    while k < number:
        f = True
        locationList = []
        locationX = random.randint(0, 3)
        locationY = random.randint(0, 3)
        locationList.append(locationX)
        locationList.append(locationY)
        for ele in listAll:
            if ele[0] == locationList[0] and ele[1] == locationList[1]:
                f = False
                break
        if f:
            listAll.append(locationList)
        if preLen == len(listAll):
            continue
        else:
            preLen = len(listAll)
        if len(listAll) == number:
            break
        k = k + 1


hw = img.shape
print(hw)
x = 0
y = 0
step = 4
xStop = hw[1]
yStop = hw[0]
hdzAll = []
hdz = []
while x < xStop:
    y = 0
    while y < yStop:
        hdz.append(img[y, x])
        y = y + step
    hdzAll.append(hdz)
    hdz = []
    x = x + step
x = 0
y = 0
i = 0
j = 0
def draw():
    for m in range(0, 4):
        for n in range(0, 4):
            # 填充灰度c
            points = [(x + m, y + n), (x + m + 1, y + n), (x + m + 1, y + n + 1), (x + m, y + n + 1)]
            flag = False
            for el in listAll:
                if m == el[0] and n == el[1]:
                    flag = True
                    break
            if flag:
                cv2.fillPoly(img, [np.array(points)], 0)
            else:
                cv2.fillPoly(img, [np.array(points)], 255)
while x < xStop:
    y = 0
    j = 0
    while y < yStop:
        c = hdzAll[i][j]
        if 0 <= c < 16:
            get_randomList(16)
            draw()
        elif 16 <= c < 32:
            get_randomList(15)
            draw()
        elif 32 <= c < 48:
            get_randomList(14)
            draw()
        elif 48 <= c < 64:
            get_randomList(13)
            draw()
        elif 64 <= c < 80:
            get_randomList(12)
            draw()
        elif 80 <= c < 96:
            get_randomList(11)
            draw()
        elif 96 <= c < 112:
            get_randomList(10)
            draw()
        elif 112 <= c < 128:
            get_randomList(9)
            draw()
        elif 128 <= c < 144:
            get_randomList(8)
            draw()
        elif 144 <= c < 160:
            get_randomList(7)
            draw()
        elif 160 <= c < 176:
            get_randomList(6)
            draw()
        elif 176 <= c < 192:
            get_randomList(5)
            draw()
        elif 192 <= c < 208:
            get_randomList(4)
            draw()
        elif 208 <= c < 224:
            get_randomList(3)
            draw()
        elif 224 <= c < 240:
            get_randomList(2)
            draw()
        elif 240 <= c <= 255:
            get_randomList(1)
            draw()
        y = y + step
        j = j + 1
    x = x + step
    i = i + 1
cv2.imwrite('c.jpg', img)
cv2.imshow('img', imutils.resize(img, width=400, height=400))
cv2.waitKey(0)
cv2.destroyAllWindows()