Exploring Image Deblurring via Blur Kernel Space (CVPR'21)

Overview

Exploring Image Deblurring via Encoded Blur Kernel Space

About the project

We introduce a method to encode the blur operators of an arbitrary dataset of sharp-blur image pairs into a blur kernel space. Assuming the encoded kernel space is close enough to in-the-wild blur operators, we propose an alternating optimization algorithm for blind image deblurring. It approximates an unseen blur operator by a kernel in the encoded space and searches for the corresponding sharp image. Due to the method's design, the encoded kernel space is fully differentiable, thus can be easily adopted in deep neural network models.

Blur kernel space

Detail of the method and experimental results can be found in our following paper:

@inproceedings{m_Tran-etal-CVPR21, 
  author = {Phong Tran and Anh Tran and Quynh Phung and Minh Hoai}, 
  title = {Explore Image Deblurring via Encoded Blur Kernel Space}, 
  year = {2021}, 
  booktitle = {Proceedings of the {IEEE} Conference on Computer Vision and Pattern Recognition (CVPR)} 
}

Please CITE our paper whenever this repository is used to help produce published results or incorporated into other software.

Open In Colab

Table of Content

Getting started

Prerequisites

  • Python >= 3.7
  • Pytorch >= 1.4.0
  • CUDA >= 10.0

Installation

git clone https://github.com/VinAIResearch/blur-kernel-space-exploring.git
cd blur-kernel-space-exploring


conda create -n BlurKernelSpace -y python=3.7
conda activate BlurKernelSpace
conda install --file requirements.txt

Training and evaluation

Preparing datasets

You can download the datasets in the model zoo section.

To use your customized dataset, your dataset must be organized as follow:

root
├── blur_imgs
    ├── 000
    ├──── 00000000.png
    ├──── 00000001.png
    ├──── ...
    ├── 001
    ├──── 00000000.png
    ├──── 00000001.png
    ├──── ...
├── sharp_imgs
    ├── 000
    ├──── 00000000.png
    ├──── 00000001.png
    ├──── ...
    ├── 001
    ├──── 00000000.png
    ├──── 00000001.png
    ├──── ...

where root, blur_imgs, and sharp_imgs folders can have arbitrary names. For example, let root, blur_imgs, sharp_imgs be REDS, train_blur, train_sharp respectively (That is, you are using the REDS training set), then use the following scripts to create the lmdb dataset:

python create_lmdb.py --H 720 --W 1280 --C 3 --img_folder REDS/train_sharp --name train_sharp_wval --save_path ../datasets/REDS/train_sharp_wval.lmdb
python create_lmdb.py --H 720 --W 1280 --C 3 --img_folder REDS/train_blur --name train_blur_wval --save_path ../datasets/REDS/train_blur_wval.lmdb

where (H, C, W) is the shape of the images (note that all images in the dataset must have the same shape), img_folder is the folder that contains the images, name is the name of the dataset, and save_path is the save destination (save_path must end with .lmdb).

When the script is finished, two folders train_sharp_wval.lmdb and train_blur_wval.lmdb will be created in ./REDS.

Training

To do image deblurring, data augmentation, and blur generation, you first need to train the blur encoding network (The F function in the paper). This is the only network that you need to train. After creating the dataset, change the value of dataroot_HQ and dataroot_LQ in options/kernel_encoding/REDS/woVAE.yml to the paths of the sharp and blur lmdb datasets that were created before, then use the following script to train the model:

python train.py -opt options/kernel_encoding/REDS/woVAE.yml

where opt is the path to yaml file that contains training configurations. You can find some default configurations in the options folder. Checkpoints, training states, and logs will be saved in experiments/modelName. You can change the configurations (learning rate, hyper-parameters, network structure, etc) in the yaml file.

Testing

Data augmentation

To augment a given dataset, first, create an lmdb dataset using scripts/create_lmdb.py as before. Then use the following script:

python data_augmentation.py --target_H=720 --target_W=1280 \
			    --source_H=720 --source_W=1280\
			    --augmented_H=256 --augmented_W=256\
                            --source_LQ_root=datasets/REDS/train_blur_wval.lmdb \
                            --source_HQ_root=datasets/REDS/train_sharp_wval.lmdb \
			    --target_HQ_root=datasets/REDS/test_sharp_wval.lmdb \
                            --save_path=results/GOPRO_augmented \
                            --num_images=10 \
                            --yml_path=options/data_augmentation/default.yml

(target_H, target_W), (source_H, source_W), and (augmented_H, augmented_W) are the desired shapes of the target images, source images, and augmented images respectively. source_LQ_root, source_HQ_root, and target_HQ_root are the paths of the lmdb datasets for the reference blur-sharp pairs and the input sharp images that were created before. num_images is the size of the augmented dataset. model_path is the path of the trained model. yml_path is the path to the model configuration file. Results will be saved in save_path.

Data augmentation examples

Generate novel blur kernels

To generate a blur image given a sharp image, use the following command:

python generate_blur.py --yml_path=options/generate_blur/default.yml \
		        --image_path=imgs/sharp_imgs/mushishi.png \
			--num_samples=10
			--save_path=./res.png

where model_path is the path of the pre-trained model, yml_path is the path of the configuration file. image_path is the path of the sharp image. After running the script, a blur image corresponding to the sharp image will be saved in save_path. Here is some expected output: kernel generating examples Note: This only works with models that were trained with --VAE flag. The size of input images must be divisible by 128.

Generic Deblurring

To deblur a blurry image, use the following command:

python generic_deblur.py --image_path imgs/blur_imgs/blur1.png --yml_path options/generic_deblur/default.yml --save_path ./res.png

where image_path is the path of the blurry image. yml_path is the path of the configuration file. The deblurred image will be saved to save_path.

Image deblurring examples

Deblurring using sharp image prior

First, you need to download the pre-trained styleGAN or styleGAN2 networks. If you want to use styleGAN, download the mapping and synthesis networks, then rename and copy them to experiments/pretrained/stylegan_mapping.pt and experiments/pretrained/stylegan_synthesis.pt respectively. If you want to use styleGAN2 instead, download the pretrained model, then rename and copy it to experiments/pretrained/stylegan2.pt.

To deblur a blurry image using styleGAN latent space as the sharp image prior, you can use one of the following commands:

python domain_specific_deblur.py --input_dir imgs/blur_faces \
		    --output_dir experiments/domain_specific_deblur/results \
		    --yml_path options/domain_specific_deblur/stylegan.yml  # Use latent space of stylegan
python domain_specific_deblur.py --input_dir imgs/blur_faces \
		    --output_dir experiments/domain_specific_deblur/results \
		    --yml_path options/domain_specific_deblur/stylegan2.yml  # Use latent space of stylegan2

Results will be saved in experiments/domain_specific_deblur/results. Note: Generally, the code still works with images that have the size divisible by 128. However, since our blur kernels are not uniform, the size of the kernel increases as the size of the image increases.

PULSE-like Deblurring examples

Model Zoo

Pretrained models and corresponding datasets are provided in the below table. After downloading the datasets and models, follow the instructions in the testing section to do data augmentation, generating blur images, or image deblurring.

Model name dataset(s) status
REDS woVAE REDS ✔️
GOPRO woVAE GOPRO ✔️
GOPRO wVAE GOPRO ✔️
GOPRO + REDS woVAE GOPRO, REDS ✔️

Notes and references

The training code is borrowed from the EDVR project: https://github.com/xinntao/EDVR

The backbone code is borrowed from the DeblurGAN project: https://github.com/KupynOrest/DeblurGAN

The styleGAN code is borrowed from the PULSE project: https://github.com/adamian98/pulse

The stylegan2 code is borrowed from https://github.com/rosinality/stylegan2-pytorch

Comments
  • Missing pretained models pth files

    Missing pretained models pth files

    Missing pth pretrained models

    http://public.vinai.io/models/blur-kernel-space-exploring/REDS_woVAE.pth http://public.vinai.io/models/blur-kernel-space-exploring/GOPRO_woVAE.pth http://public.vinai.io/models/blur-kernel-space-exploring/GOPRO_wVAE.pth http://public.vinai.io/models/blur-kernel-space-exploring/mix_woVAE.pth

    All the above links do not work.

    Any other alternative sources for downloading?

    opened by ckinsung85 1
  • Generic_deblur.py result image using pretrained weights is different from the paper.

    Generic_deblur.py result image using pretrained weights is different from the paper.

    Hi. I'm trying to deblur face01.png image of blur_faces. While I was running generic_deblur.py with default settings with pretrained weights of GOPRO_woVAE.pth and the results were unsatisfactory. I checked that the generate_blur.py made satisfactory results of blur generating process. For prerequisites, I'm using the versons below.

    • Python = 3.9.5
    • Pytorch = 1.9.0
    • CUDA = 10.1

    I tried to use Pytorch version of 1.4.0 but version error occured, so I used 1.9.0. I want to get results in the paper for face01.png image, and if there is anything I should tune or change, please let me know. Thank you for your help.

    opened by sjmin9868 1
  • model license?

    model license?

    Hello! Thank you so much for sharing the code and the model.

    I would like to ask, what protocol is the pre-training model? Can I use it in a business project?

    Your reply is very much needed.

    opened by go-to-here 1
  • Output of the PostProcessBlock added to the input sharp image.

    Output of the PostProcessBlock added to the input sharp image.

    Hi! First of all congratulations for the work.

    In the code, the output of the postprocessblock is added to the input sharp image in order to generate the blur image. In the paper i do not find any place that says explicitly that the output is a residual to be added to the residual in order to create the blur image. is it a bug? should be added? Or F computes directly the blur image?

    Thanks!

    opened by pvitoria 1
  • G function training for other blur kernel family

    G function training for other blur kernel family

    In Readme, the author said “To do image deblurring, data augmentation, and blur generation, you first need to train the blur encoding network (The F function in the paper). This is the only network that you need to train.“ However, after I retrained the F function using out-of-focus blur- sharp image pairs and used it in the image deblurring script "generic_deblur.py" to deblur out-of-focus images, the results are not yet satisfying.

    I trained with a batch_size of 13 and iteration of 600,000. The final loss is around 1e03, and the training datasets volume is around 3000 image pairs.

    I wonder if I need to retrain the G function as well, if I am using the network to deblur out-of-focus images. If needed, is it possible to provide a script that trains both G and F functions?

    Thank you!

    opened by Laviness 1
  • How to create file

    How to create file "warmup_k_path"?

    Thank you for this repo is really awesome. But I have a small question: how is the "warmup_k_path" file created, what is it used for in the deblur process.

    opened by viet24dung 1
  • When running with 512x512, the code errors out

    When running with 512x512, the code errors out

    Hi, I'm trying to run domain_specific_deblur.py with styleGAN2 on 512x512 dimension with pretrained models. However, after I fixed the latent and noise dimentions in dsd_stylegan2.py, the code still errors out on unet_parts.py. Is 512x512 supported?

    The stacktrace is as below:

    Traceback (most recent call last):
      File "domain_specific_deblur.py", line 84, in <module>
        for j, (HR, LR) in enumerate(model(ref_im)):
      File "/deblur/blur-kernel-space-exploring/models/dsd/dsd.py", line 188, in forward
        self.optimize_x_step(epoch)
      File "/deblur/blur-kernel-space-exploring/models/dsd/dsd.py", line 150, in optimize_x_step
        loss, loss_dict = self.loss_builder(latent_in, self.gen_im, self.gen_ker, epoch)
      File "//anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "/deblur/blur-kernel-space-exploring/models/losses/dsd_loss.py", line 108, in forward
        "gen_im_lr": self.D.adaptKernel(gen_im, kernel),
      File "/work/deblur/blur-kernel-space-exploring/models/kernel_encoding/kernel_wizard.py", line 157, in adaptKernel
        out = self.adapter(x_sharp, kernel)
      File "/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "//work/deblur/blur-kernel-space-exploring/models/kernel_encoding/kernel_wizard.py", line 99, in forward
        return self.model(x, k)
      File "/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "/work/deblur/blur-kernel-space-exploring/models/backbones/unet_parts.py", line 103, in forward
        return self.up(self.submodule(self.down(x), noise))
      File "/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "/work/deblur/blur-kernel-space-exploring/models/backbones/unet_parts.py", line 109, in forward
        return torch.cat((self.up(self.submodule(self.down(x), noise)), x), dim=1)
      File "/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "/work/deblur/blur-kernel-space-exploring/models/backbones/unet_parts.py", line 109, in forward
        return torch.cat((self.up(self.submodule(self.down(x), noise)), x), dim=1)
      File "/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "/work/deblur/blur-kernel-space-exploring/models/backbones/unet_parts.py", line 109, in forward
        return torch.cat((self.up(self.submodule(self.down(x), noise)), x), dim=1)
      File "/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 727, in _call_impl
        result = self.forward(*input, **kwargs)
      File "/deblur/blur-kernel-space-exploring/models/backbones/unet_parts.py", line 107, in forward
        return torch.cat((self.up(torch.cat((self.down(x), noise), dim=1)), x), dim=1)
    RuntimeError: Sizes of tensors must match except in dimension 2. Got 2 and 4 (The offending index is 0)
    

    Thank you for your help!

    opened by nairb2020 1
  • Pretrained model not downloadable?

    Pretrained model not downloadable?

    Hi,

    Thanks for this awesome work. It looks like the pretrained models don't seem to be available to download? I tried to download all 4 of them (e.g. http://public.vinai.io/models/blur-kernel-space-exploring/REDS_woVAE.pth) They don't seem to be downloadable... Is it possible to provide a public google drive link?

    Thank you so much,

    opened by nairb2020 1
  • Is there naming confusion between G & F from paper and the G & F from the code?

    Is there naming confusion between G & F from paper and the G & F from the code?

    Please check this https://github.com/VinAIResearch/blur-kernel-space-exploring/blob/619c9b3b33961ef9311399d7cbbf92050a0c6b51/models/kernel_encoding/kernel_wizard.py#L72

    I think you mean it to be F.

    opened by nile649 1
  • generate_blur

    generate_blur

    To transfer the motion blur between the image pair (x, y) to a new image xˆ, we can simply compute: yˆ := F(ˆx, G(x, y)). however, when i set the x^ same as the x, the output y^ is largely different from y. It seems that the extracted kernel is not that accurate。

    opened by StephanPan 0
  • about kernel

    about kernel

    first of all, impressive work!

    1. I am confused about the form of kernel. Ki is the kernel of uniform blur or can also be kernel of non-uniform blur?
    2. Why the regularization term on the L2 norm of the kernel k works?
    opened by YeHuanjie 0
  • TypeError: cannot create 'generator' instances

    TypeError: cannot create 'generator' instances

    I run in command line python domain_specific_deblur.py --input_dir imgs/blur_faces
    --output_dir experiments/domain_specific_deblur/results
    --yml_path options/domain_specific_deblur/stylegan.yml

    and using 2 GPU, but I have error so How do I can fix this? image

    opened by ntquyen11 0
  • run the generate_blur.py  ocur and i got a error

    run the generate_blur.py ocur and i got a error

    generate_blur.py:30: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details. opt = yaml.load(f)["KernelWizard"] Sample #0/10 Traceback (most recent call last): File "generate_blur.py", line 53, in main() File "generate_blur.py", line 45, in main LQ_tensor = model.adaptKernel(HQ_tensor, kernel) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\kernel_encoding\kernel_wizard.py", line 157, in adaptKernel out = self.adapter(x_sharp, kernel) File "C:\Users\Xun.Huang\Anaconda3\envs\pytorch-env\lib\site-packages\torch\nn\modules\module.py", line 1051, in _call_impl return forward_call(*input, **kwargs) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\kernel_encoding\kernel_wizard.py", line 99, in forward return self.model(x, k) File "C:\Users\Xun.Huang\Anaconda3\envs\pytorch-env\lib\site-packages\torch\nn\modules\module.py", line 1051, in _call_impl return forward_call(*input, **kwargs) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\backbones\unet_parts.py", line 103, in forward return self.up(self.submodule(self.down(x), noise)) File "C:\Users\Xun.Huang\Anaconda3\envs\pytorch-env\lib\site-packages\torch\nn\modules\module.py", line 1051, in _call_impl return forward_call(*input, **kwargs) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\backbones\unet_parts.py", line 109, in forward return torch.cat((self.up(self.submodule(self.down(x), noise)), x), dim=1) File "C:\Users\Xun.Huang\Anaconda3\envs\pytorch-env\lib\site-packages\torch\nn\modules\module.py", line 1051, in _call_impl return forward_call(*input, **kwargs) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\backbones\unet_parts.py", line 109, in forward return torch.cat((self.up(self.submodule(self.down(x), noise)), x), dim=1) File "C:\Users\Xun.Huang\Anaconda3\envs\pytorch-env\lib\site-packages\torch\nn\modules\module.py", line 1051, in _call_impl return forward_call(*input, **kwargs) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\backbones\unet_parts.py", line 109, in forward return torch.cat((self.up(self.submodule(self.down(x), noise)), x), dim=1) File "C:\Users\Xun.Huang\Anaconda3\envs\pytorch-env\lib\site-packages\torch\nn\modules\module.py", line 1051, in _call_impl return forward_call(*input, **kwargs) File "C:\Users\Xun.Huang\blur-kernel-space-exploring\models\backbones\unet_parts.py", line 107, in forward return torch.cat((self.up(torch.cat((self.down(x), noise), dim=1)), x), dim=1) RuntimeError: torch.cat(): Sizes of tensors must match except in dimension 1. Got 4 and 2 in dimension 2 (The offending index is 1)

    the pre-trained model file is GOPRO_wVAE . anyone can help me? Should i change the dim=1 to dim=2?

    opened by alexHxun 0
Owner
VinAI Research
VinAI Research
[ICCV 2021] Official Tensorflow Implementation for "Single Image Defocus Deblurring Using Kernel-Sharing Parallel Atrous Convolutions"

KPAC: Kernel-Sharing Parallel Atrous Convolutional block This repository contains the official Tensorflow implementation of the following paper: Singl

Hyeongseok Son 50 Dec 29, 2022
PyTorch implementation of Graph Convolutional Networks in Feature Space for Image Deblurring and Super-resolution, IJCNN 2021.

GCResNet PyTorch implementation of Graph Convolutional Networks in Feature Space for Image Deblurring and Super-resolution, IJCNN 2021. The code will

null 11 May 19, 2022
[CVPR21] LightTrack: Finding Lightweight Neural Network for Object Tracking via One-Shot Architecture Search

LightTrack: Finding Lightweight Neural Networks for Object Tracking via One-Shot Architecture Search The official implementation of the paper LightTra

Multimedia Research 290 Dec 24, 2022
Repo for FUZE project. I will also publish some Linux kernel LPE exploits for various real world kernel vulnerabilities here. the samples are uploaded for education purposes for red and blue teams.

Linux_kernel_exploits Some Linux kernel exploits for various real world kernel vulnerabilities here. More exploits are yet to come. This repo contains

Wei Wu 472 Dec 21, 2022
FuseDream: Training-Free Text-to-Image Generationwith Improved CLIP+GAN Space OptimizationFuseDream: Training-Free Text-to-Image Generationwith Improved CLIP+GAN Space Optimization

FuseDream This repo contains code for our paper (paper link): FuseDream: Training-Free Text-to-Image Generation with Improved CLIP+GAN Space Optimizat

XCL 191 Dec 31, 2022
Code for HLA-Face: Joint High-Low Adaptation for Low Light Face Detection (CVPR21)

HLA-Face: Joint High-Low Adaptation for Low Light Face Detection The official PyTorch implementation for HLA-Face: Joint High-Low Adaptation for Low L

Wenjing Wang 77 Dec 8, 2022
Repository relating to the CVPR21 paper TimeLens: Event-based Video Frame Interpolation

TimeLens: Event-based Video Frame Interpolation This repository is about the High Speed Event and RGB (HS-ERGB) dataset, used in the 2021 CVPR paper T

Robotics and Perception Group 544 Dec 19, 2022
Released code for Objects are Different: Flexible Monocular 3D Object Detection, CVPR21

MonoFlex Released code for Objects are Different: Flexible Monocular 3D Object Detection, CVPR21. Work in progress. Installation This repo is tested w

Yunpeng 169 Dec 6, 2022
Official repository for CVPR21 paper "Deep Stable Learning for Out-Of-Distribution Generalization".

StableNet StableNet is a deep stable learning method for out-of-distribution generalization. This is the official repo for CVPR21 paper "Deep Stable L

null 120 Dec 28, 2022
SimDeblur is a simple framework for image and video deblurring, implemented by PyTorch

SimDeblur (Simple Deblurring) is an open source framework for image and video deblurring toolbox based on PyTorch, which contains most deep-learning based state-of-the-art deblurring algorithms. It is easy to implement your own image or video deblurring or other restoration algorithms.

null 220 Jan 7, 2023
Image Deblurring using Generative Adversarial Networks

DeblurGAN arXiv Paper Version Pytorch implementation of the paper DeblurGAN: Blind Motion Deblurring Using Conditional Adversarial Networks. Our netwo

Orest Kupyn 2.2k Jan 1, 2023
A curated list of resources for Image and Video Deblurring

A curated list of resources for Image and Video Deblurring

Subeesh Vasu 1.7k Jan 1, 2023
[CVPR 2021] Official PyTorch Implementation for "Iterative Filter Adaptive Network for Single Image Defocus Deblurring"

IFAN: Iterative Filter Adaptive Network for Single Image Defocus Deblurring Checkout for the demo (GUI/Google Colab)! The GUI version might occasional

Junyong Lee 173 Dec 30, 2022
A face dataset generator with out-of-focus blur detection and dynamic interval adjustment.

A face dataset generator with out-of-focus blur detection and dynamic interval adjustment.

Yutian Liu 2 Jan 29, 2022
Space robot - (Course Project) Using the space robot to capture the target satellite that is disabled and spinning, then stabilize and fix it up

Space robot - (Course Project) Using the space robot to capture the target satellite that is disabled and spinning, then stabilize and fix it up

Mingrui Yu 3 Jan 7, 2022
Official PyTorch code for CVPR 2020 paper "Deep Active Learning for Biased Datasets via Fisher Kernel Self-Supervision"

Deep Active Learning for Biased Datasets via Fisher Kernel Self-Supervision https://arxiv.org/abs/2003.00393 Abstract Active learning (AL) aims to min

Denis 29 Nov 21, 2022
DeFMO: Deblurring and Shape Recovery of Fast Moving Objects (CVPR 2021)

Evaluation, Training, Demo, and Inference of DeFMO DeFMO: Deblurring and Shape Recovery of Fast Moving Objects (CVPR 2021) Denys Rozumnyi, Martin R. O

Denys Rozumnyi 139 Dec 26, 2022
Towards Rolling Shutter Correction and Deblurring in Dynamic Scenes (CVPR2021)

RSCD (BS-RSCD & JCD) Towards Rolling Shutter Correction and Deblurring in Dynamic Scenes (CVPR2021) by Zhihang Zhong, Yinqiang Zheng, Imari Sato We co

null 81 Dec 15, 2022
Cascaded Deep Video Deblurring Using Temporal Sharpness Prior and Non-local Spatial-Temporal Similarity

This repository is the official PyTorch implementation of Cascaded Deep Video Deblurring Using Temporal Sharpness Prior and Non-local Spatial-Temporal Similarity

hippopmonkey 4 Dec 11, 2022