Keras ではConv2Dクラスが畳み込み層のクラスになります。
では早速、Conv2D クラスを使って前ページの例を演算してみましょう(ソース1)。

ソース 1: Keras の Conv2D クラスで畳み込み演算をする例
import tensorflow as tf

'''
# CUDA を有効にしている時に「UnknownError:  Failed to get convolution algorithm.」というエラーが
# 出て動かない時はコメントアウトを外し、カーネルを再起動してから実行してください
from tensorflow.compat.v1.keras.backend import set_session
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
config.log_device_placement = True
sess = tf.compat.v1.Session(config=config)
set_session(sess)
'''

import numpy as np

rows = 4 # 画像の高さ
cols = 4 # 画像の幅
Cin = 2  # 入力画像のチャンネル数
Cout = 2 # 出力画像のチャンネル数
kernel_size = 3 # カーネルサイズ

# 入力画像の 0 番目のチャンネル
data_0 = np.array(
    [
        [ 1, 2, 3, 0 ],
        [ 2, 0, 1, 2 ],
        [ 0, 1, 2, 0 ],
        [ 0, 0, 3, 1 ]
    ]
)

# 入力画像の 1 番目のチャンネル
data_1 = np.array(
    [
        [ 0, 2, 1, 3 ],
        [ 1, 1, 0, 1 ],
        [ 2, 2, 3, 2 ],
        [ 3, 1, 0, 0 ]
    ]
)

# 入力画像の 0 番目のチャンネルと出力画像の 0 番目のチャンネルに対するカーネル
kernel_00 = np.array(
    [ 
        [ 0, 1, 0 ],
        [ 2, 0, 3 ],
        [ 0, 4, 0 ]
    ]
)

# 入力画像の 0 番目のチャンネルと出力画像の 1 番目のチャンネルに対するカーネル
kernel_01 = np.array(
    [ 
        [ 2, 0, 1 ],
        [ 0, 1, 0 ],
        [ 1, 0, 3 ]
    ]
)

# 入力画像の 1 番目のチャンネルと出力画像の 0 番目のチャンネルに対するカーネル
kernel_10 = np.array(
    [ 
        [ 0, 0, 0 ],
        [ 0, 1, 0 ],
        [ 0, 0, 0 ]
    ]
)

# 入力画像の 1 番目のチャンネルと出力画像の 1 番目のチャンネルに対するカーネル
kernel_11 = np.array(
    [ 
        [ 1, 0, 0 ],
        [ 0, 2, 3 ],
        [ 2, 0, 0 ]
    ]
)

# 出力画像の 0 番目のチャンネルに対するバイアス
b_0 = 1

# 出力画像の 1 番目のチャンネルに対するバイアス
b_1 = -1

# 入力画像全体: shape = (データ数, rows, cols, Cin) の 4 階テンソル
data = tf.constant( [ np.stack( [data_0,data_1], -1) ], dtype=tf.float32 )

# カーネル全体: shape = (kernel_size, kernel_size, Cin * Cout) の 3 階テンソル 
kernel = np.stack( [kernel_00,kernel_01,kernel_10,kernel_11], -1)

# バイアス全体 :  shape = (Cout) のベクトル
b = [b_0, b_1]

# モデル作成
model = tf.keras.Sequential()
model.add( tf.keras.Input(shape=(rows,cols,Cin,)))
model.add( tf.keras.layers.Conv2D( Cout, (kernel_size,kernel_size), 
                                  kernel_initializer=tf.keras.initializers.Constant(kernel), 
                                  bias_initializer=tf.keras.initializers.Constant(b) ))
model.summary()

# 畳み込み演算
y = model.predict( data )

print('')
np.set_printoptions(precision=0, suppress=True)
for i in range(Cin):
    print(f'data_{i}=')
    print( data[0,:,:,i].numpy())
    print('')

for i in range(Cin):
    for i2 in range(Cout):
        print(f'kernel_{i}{i2} = ')
        print(kernel[:,:,Cin*i+i2])
        print('')

for i in range(Cout):
    print(f'b_{i} = {b[i]}')
    print('')

for i in range(Cout):
    print(f'y_{i} =')
    print(y[0,:,:,i])
    print('')

基本的にはMLPと同様に Sequential モデルを作成し、predict メソッドで出力を求めますが、

model.add( tf.keras.layers.Conv2D( Cout, (kernel_size,kernel_size), 
                                  kernel_initializer=tf.keras.initializers.Constant(kernel), 
                                  bias_initializer=tf.keras.initializers.Constant(b) ))

の行で畳み込み層(Conv2Dクラス)をモデルに追加しています。

では上のソースの実行結果を以下に示します。

ソース 1の実行結果
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_2 (Conv2D)            (None, 2, 2, 2)           38        
=================================================================
Total params: 38
Trainable params: 38
Non-trainable params: 0
_________________________________________________________________

data_0=
[[1. 2. 3. 0.]
 [2. 0. 1. 2.]
 [0. 1. 2. 0.]
 [0. 0. 3. 1.]]

data_1=
[[0. 2. 1. 3.]
 [1. 1. 0. 1.]
 [2. 2. 3. 2.]
 [3. 1. 0. 0.]]

kernel_00 = 
[[0 1 0]
 [2 0 3]
 [0 4 0]]

kernel_01 = 
[[2 0 1]
 [0 1 0]
 [1 0 3]]

kernel_10 = 
[[0 0 0]
 [0 1 0]
 [0 0 0]]

kernel_11 = 
[[1 0 0]
 [0 2 3]
 [2 0 0]]

b_0 = 1

b_1 = -1

y_0 =
[[15. 18.]
 [ 9. 19.]]

y_1 =
[[16. 14.]
 [34. 21.]]

ところでモデル概要を見るとパラメータの個数が Total param : 38 個となっています。
カーネル1つに含まれるパラメータ数は 9 個、カーネルは計 4 個、バイアスは計 2 個より、9*4+2=38 なので確かに合っています。