Configure the NSFW checker at install time with default on (#1624)

* configure the NSFW checker at install time with default on

1. Changes the --safety_checker argument to --nsfw_checker and
--no-nsfw_checker. The original argument is recognized for backward
compatibility.

2. The configure script asks users whether to enable the checker
(default yes). Also offers users ability to select default sampler and
number of generation steps.

3.Enables the pasting of the caution icon on blurred images when
InvokeAI is installed into the package directory.

4. Adds documentation for the NSFW checker, including caveats about
accuracy, memory requirements, and intermediate image dispaly.

* use better fitting icon

* NSFW defaults false for testing

* set default back to nsfw active

Co-authored-by: Matthias Wild <40327258+mauwii@users.noreply.github.com>
Co-authored-by: mauwii <Mauwii@outlook.de>
This commit is contained in:
Lincoln Stein 2022-11-30 14:50:57 -05:00 committed by GitHub
parent 22133392b2
commit 971f5c5ab1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 165 additions and 30 deletions

89
docs/features/NSFW.md Normal file
View File

@ -0,0 +1,89 @@
---
title: The NSFW Checker
---
# :material-image-off: NSFW Checker
## The NSFW ("Safety") Checker
The Stable Diffusion image generation models will produce sexual
imagery if deliberately prompted, and will occasionally produce such
images when this is not intended. Such images are colloquially known
as "Not Safe for Work" (NSFW). This behavior is due to the nature of
the training set that Stable Diffusion was trained on, which culled
millions of "aesthetic" images from the Internet.
You may not wish to be exposed to these images, and in some
jurisdictions it may be illegal to publicly distribute such imagery,
including mounting a publicly-available server that provides
unfiltered images to the public. Furthermore, the [Stable Diffusion
weights
License](https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE-ModelWeights.txt)
forbids the model from being used to "exploit any of the
vulnerabilities of a specific group of persons."
For these reasons Stable Diffusion offers a "safety checker," a
machine learning model trained to recognize potentially disturbing
imagery. When a potentially NSFW image is detected, the checker will
blur the image and paste a warning icon on top. The checker can be
turned on and off on the command line using `--nsfw_checker` and
`--no-nsfw_checker`.
At installation time, InvokeAI will ask whether the checker should be
activated by default (neither argument given on the command line). The
response is stored in the InvokeAI initialization file (usually
`.invokeai` in your home directory). You can change the default at any
time by opening this file in a text editor and commenting or
uncommenting the line `--nsfw_checker`.
## Caveats
There are a number of caveats that you need to be aware of.
### Accuracy
The checker is [not perfect](https://arxiv.org/abs/2210.04610).It will
occasionally flag innocuous images (false positives), and will
frequently miss violent and gory imagery (false negatives). It rarely
fails to flag sexual imagery, but this has been known to happen. For
these reasons, the InvokeAI team prefers to refer to the software as a
"NSFW Checker" rather than "safety checker."
### Memory Usage and Performance
The NSFW checker consumes an additional 1.2G of GPU VRAM on top of the
3.4G of VRAM used by Stable Diffusion v1.5 (this is with
half-precision arithmetic). This means that the checker will not run
successfully on GPU cards with less than 6GB VRAM, and will reduce the
size of the images that you can produce.
The checker also introduces a slight performance penalty. Images will
take ~1 second longer to generate when the checker is
activated. Generally this is not noticeable.
### Intermediate Images in the Web UI
The checker only operates on the final image produced by the Stable
Diffusion algorithm. If you are using the Web UI and have enabled the
display of intermediate images, you will briefly be exposed to a
low-resolution (mosaicized) version of the final image before it is
flagged by the checker and replaced by a fully blurred version. You
are encouraged to turn **off** intermediate image rendering when you
are using the checker. Future versions of InvokeAI will apply
additional blurring to intermediate images when the checker is active.
### Watermarking
InvokeAI does not apply any sort of watermark to images it
generates. However, it does write metadata into the PNG data area,
including the prompt used to generate the image and relevant parameter
settings. These fields can be examined using the `sd-metadata.py`
script that comes with the InvokeAI package.
Note that several other Stable Diffusion distributions offer
wavelet-based "invisible" watermarking. We have experimented with the
library used to generate these watermarks and have reached the
conclusion that while the watermarking library may be adding
watermarks to PNG images, the currently available version is unable to
retrieve them successfully. If and when a functioning version of the
library becomes available, we will offer this feature as well.

View File

@ -468,7 +468,7 @@ class Args(object):
action=argparse.BooleanOptionalAction, action=argparse.BooleanOptionalAction,
dest='safety_checker', dest='safety_checker',
default=False, default=False,
help='Check for and blur potentially NSFW images.', help='Check for and blur potentially NSFW images. Use --no-nsfw_checker to disable.',
) )
model_group.add_argument( model_group.add_argument(
'--patchmatch', '--patchmatch',

View File

@ -6,6 +6,7 @@ import torch
import numpy as np import numpy as np
import random import random
import os import os
import os.path as osp
import traceback import traceback
from tqdm import tqdm, trange from tqdm import tqdm, trange
from PIL import Image, ImageFilter, ImageChops from PIL import Image, ImageFilter, ImageChops
@ -32,6 +33,7 @@ class Generator():
self.with_variations = [] self.with_variations = []
self.use_mps_noise = False self.use_mps_noise = False
self.free_gpu_mem = None self.free_gpu_mem = None
self.caution_img = None
# this is going to be overridden in img2img.py, txt2img.py and inpaint.py # this is going to be overridden in img2img.py, txt2img.py and inpaint.py
def get_make_image(self,prompt,**kwargs): def get_make_image(self,prompt,**kwargs):
@ -290,13 +292,29 @@ class Generator():
def blur(self,input): def blur(self,input):
blurry = input.filter(filter=ImageFilter.GaussianBlur(radius=32)) blurry = input.filter(filter=ImageFilter.GaussianBlur(radius=32))
try: try:
caution = Image.open(CAUTION_IMG) caution = self.get_caution_img()
caution = caution.resize((caution.width // 2, caution.height //2)) if caution:
blurry.paste(caution,(0,0),caution) blurry.paste(caution,(0,0),caution)
except FileNotFoundError: except FileNotFoundError:
pass pass
return blurry return blurry
def get_caution_img(self):
if self.caution_img:
return self.caution_img
# Find the caution image. If we are installed in the package directory it will
# be six levels up. If we are in the repo directory it will be three levels up.
for dots in ('../../..','../../../../../..'):
caution_path = osp.join(osp.dirname(__file__),dots,CAUTION_IMG)
if osp.exists(caution_path):
path = caution_path
break
if not path:
return
caution = Image.open(path)
self.caution_img = caution.resize((caution.width // 2, caution.height //2))
return self.caution_img
# this is a handy routine for debugging use. Given a generated sample, # this is a handy routine for debugging use. Given a generated sample,
# convert it into a PNG image and store it at the indicated path # convert it into a PNG image and store it at the indicated path
def save_sample(self, sample, filepath): def save_sample(self, sample, filepath):

View File

@ -70,10 +70,10 @@ Web version:
Command-line version: Command-line version:
python scripts/invoke.py python scripts/invoke.py
Remember to activate that 'invokeai' environment before running invoke.py. If you installed manually, remember to activate the 'invokeai'
environment before running invoke.py. If you installed using the
Or, if you used one of the automated installers, execute "invoke.sh" (Linux/Mac) automated installation script, execute "invoke.sh" (Linux/Mac) or
or "invoke.bat" (Windows) to start the script. "invoke.bat" (Windows) to start InvokeAI.
Have fun! Have fun!
''' '''
@ -243,10 +243,10 @@ def download_weight_datasets(models:dict, access_token:str):
for mod in models.keys(): for mod in models.keys():
repo_id = Datasets[mod]['repo_id'] repo_id = Datasets[mod]['repo_id']
filename = Datasets[mod]['file'] filename = Datasets[mod]['file']
print(os.path.join(Globals.root,Model_dir,Weights_dir), file=sys.stderr) dest = os.path.join(Globals.root,Model_dir,Weights_dir)
success = hf_download_with_resume( success = hf_download_with_resume(
repo_id=repo_id, repo_id=repo_id,
model_dir=os.path.join(Globals.root,Model_dir,Weights_dir), model_dir=dest,
model_name=filename, model_name=filename,
access_token=access_token access_token=access_token
) )
@ -494,12 +494,12 @@ def download_clipseg():
#------------------------------------- #-------------------------------------
def download_safety_checker(): def download_safety_checker():
print('Installing safety model for NSFW content detection...',file=sys.stderr) print('Installing model for NSFW content detection...',file=sys.stderr)
try: try:
from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker
from transformers import AutoFeatureExtractor from transformers import AutoFeatureExtractor
except ModuleNotFoundError: except ModuleNotFoundError:
print('Error installing safety checker model:') print('Error installing NSFW checker model:')
print(traceback.format_exc()) print(traceback.format_exc())
return return
safety_model_id = "CompVis/stable-diffusion-safety-checker" safety_model_id = "CompVis/stable-diffusion-safety-checker"
@ -520,6 +520,7 @@ def download_weights(opt:dict):
return return
else: else:
print('** Cannot download models because no Hugging Face access token could be found. Please re-run without --yes') print('** Cannot download models because no Hugging Face access token could be found. Please re-run without --yes')
return
else: else:
choice = user_wants_to_download_weights() choice = user_wants_to_download_weights()
@ -584,7 +585,7 @@ def select_outputs(root:str,yes_to_all:bool=False):
#------------------------------------- #-------------------------------------
def initialize_rootdir(root:str,yes_to_all:bool=False): def initialize_rootdir(root:str,yes_to_all:bool=False):
assert os.path.exists('./configs'),'Run this script from within the top level of the InvokeAI source code directory, "InvokeAI"' assert os.path.exists('./configs'),'Run this script from within the InvokeAI source code directory, "InvokeAI" or the runtime directory "invokeai".'
print(f'** INITIALIZING INVOKEAI RUNTIME DIRECTORY **') print(f'** INITIALIZING INVOKEAI RUNTIME DIRECTORY **')
root_selected = False root_selected = False
@ -603,6 +604,37 @@ def initialize_rootdir(root:str,yes_to_all:bool=False):
print(f'\nYou may change the chosen directories at any time by editing the --root and --outdir options in "{Globals.initfile}",') print(f'\nYou may change the chosen directories at any time by editing the --root and --outdir options in "{Globals.initfile}",')
print(f'You may also change the runtime directory by setting the environment variable INVOKEAI_ROOT.\n') print(f'You may also change the runtime directory by setting the environment variable INVOKEAI_ROOT.\n')
enable_safety_checker = True
default_sampler = 'k_heun'
default_steps = '20' # deliberately a string - see test below
sampler_choices =['ddim','k_dpm_2_a','k_dpm_2','k_euler_a','k_euler','k_heun','k_lms','plms']
if not yes_to_all:
print('The NSFW (not safe for work) checker blurs out images that potentially contain sexual imagery.')
print('It can be selectively enabled at run time with --nsfw_checker, and disabled with --no-nsfw_checker.')
print('The following option will set whether the checker is enabled by default. Like other options, you can')
print(f'change this setting later by editing the file {Globals.initfile}.')
enable_safety_checker = yes_or_no('Enable the NSFW checker by default?',enable_safety_checker)
print('\nThe next choice selects the sampler to use by default. Samplers have different speed/performance')
print('tradeoffs. If you are not sure what to select, accept the default.')
sampler = None
while sampler not in sampler_choices:
sampler = input(f'Default sampler to use? ({", ".join(sampler_choices)}) [{default_sampler}]:') or default_sampler
print('\nThe number of denoising steps affects both the speed and quality of the images generated.')
print('Higher steps often (but not always) increases the quality of the image, but increases image')
print('generation time. This can be changed at run time. Accept the default if you are unsure.')
steps = ''
while not steps.isnumeric():
steps = input(f'Default number of steps to use during generation? [{default_steps}]:') or default_steps
else:
sampler = default_sampler
steps = default_steps
safety_checker = '--nsfw_checker' if enable_safety_checker else '--no-nsfw_checker'
for name in ('models','configs','embeddings'): for name in ('models','configs','embeddings'):
os.makedirs(os.path.join(root,name), exist_ok=True) os.makedirs(os.path.join(root,name), exist_ok=True)
for src in (['configs']): for src in (['configs']):
@ -612,7 +644,7 @@ def initialize_rootdir(root:str,yes_to_all:bool=False):
os.makedirs(outputs, exist_ok=True) os.makedirs(outputs, exist_ok=True)
init_file = os.path.expanduser(Globals.initfile) init_file = os.path.expanduser(Globals.initfile)
if not os.path.exists(init_file):
print(f'Creating the initialization file at "{init_file}".\n') print(f'Creating the initialization file at "{init_file}".\n')
with open(init_file,'w') as f: with open(init_file,'w') as f:
f.write(f'''# InvokeAI initialization file f.write(f'''# InvokeAI initialization file
@ -626,23 +658,18 @@ def initialize_rootdir(root:str,yes_to_all:bool=False):
# the --outdir option controls the default location of image files. # the --outdir option controls the default location of image files.
--outdir="{outputs}" --outdir="{outputs}"
# generation arguments
{safety_checker}
--sampler={sampler}
--steps={steps}
# You may place other frequently-used startup commands here, one or more per line. # You may place other frequently-used startup commands here, one or more per line.
# Examples: # Examples:
# --web --host=0.0.0.0 # --web --host=0.0.0.0
# --steps=20 # --steps=20
# -Ak_euler_a -C10.0 # -Ak_euler_a -C10.0
# #
''' ''')
)
else:
print(f'Updating the initialization file at "{init_file}".\n')
with open(init_file,'r') as infile, open(f'{init_file}.tmp','w') as outfile:
for line in infile.readlines():
if not line.startswith('--root') and not line.startswith('--outdir'):
outfile.write(line)
outfile.write(f'--root="{root}"\n')
outfile.write(f'--outdir="{outputs}"\n')
os.replace(f'{init_file}.tmp',init_file)
#------------------------------------- #-------------------------------------
class ProgressBar(): class ProgressBar():

View File

@ -81,6 +81,7 @@ setup(
'scripts/preload_models.py', 'scripts/images2prompt.py','scripts/merge_embeddings.py' 'scripts/preload_models.py', 'scripts/images2prompt.py','scripts/merge_embeddings.py'
], ],
data_files=[('frontend/dist',list_files('frontend/dist')), data_files=[('frontend/dist',list_files('frontend/dist')),
('frontend/dist/assets',list_files('frontend/dist/assets')) ('frontend/dist/assets',list_files('frontend/dist/assets')),
('assets',['assets/caution.png']),
], ],
) )