experimental: Add CV2 Infill

This commit is contained in:
blessedcoolant 2023-09-02 04:48:18 +12:00
parent 7a295cbfd5
commit 1a9f552a75
6 changed files with 178 additions and 29 deletions

View File

@ -1,19 +1,19 @@
import typing import typing
from enum import Enum from enum import Enum
from pathlib import Path
from fastapi import Body from fastapi import Body
from fastapi.routing import APIRouter from fastapi.routing import APIRouter
from pathlib import Path
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from invokeai.app.invocations.upscale import ESRGAN_MODELS
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
from invokeai.backend.image_util.patchmatch import PatchMatch from invokeai.backend.image_util.patchmatch import PatchMatch
from invokeai.backend.image_util.safety_checker import SafetyChecker from invokeai.backend.image_util.safety_checker import SafetyChecker
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark from invokeai.backend.util.logging import logging
from invokeai.app.invocations.upscale import ESRGAN_MODELS
from invokeai.version import __version__ from invokeai.version import __version__
from ..dependencies import ApiDependencies from ..dependencies import ApiDependencies
from invokeai.backend.util.logging import logging
class LogLevel(int, Enum): class LogLevel(int, Enum):
@ -55,7 +55,7 @@ async def get_version() -> AppVersion:
@app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig) @app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig)
async def get_config() -> AppConfig: async def get_config() -> AppConfig:
infill_methods = ["tile", "lama"] infill_methods = ["tile", "lama", "cv2"]
if PatchMatch.patchmatch_available(): if PatchMatch.patchmatch_available():
infill_methods.append("patchmatch") infill_methods.append("patchmatch")

View File

@ -8,6 +8,7 @@ from PIL import Image, ImageOps
from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput
from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.app.util.misc import SEED_MAX, get_random_seed
from invokeai.backend.image_util.cv2_inpaint import cv2_inpaint
from invokeai.backend.image_util.lama import LaMA from invokeai.backend.image_util.lama import LaMA
from invokeai.backend.image_util.patchmatch import PatchMatch from invokeai.backend.image_util.patchmatch import PatchMatch
@ -16,11 +17,7 @@ from .baseinvocation import BaseInvocation, InputField, InvocationContext, invoc
def infill_methods() -> list[str]: def infill_methods() -> list[str]:
methods = [ methods = ["tile", "solid", "lama", "cv2"]
"tile",
"solid",
"lama",
]
if PatchMatch.patchmatch_available(): if PatchMatch.patchmatch_available():
methods.insert(0, "patchmatch") methods.insert(0, "patchmatch")
return methods return methods
@ -49,6 +46,10 @@ def infill_patchmatch(im: Image.Image) -> Image.Image:
return im_patched return im_patched
def infill_cv2(im: Image.Image) -> Image.Image:
return cv2_inpaint(im)
def get_tile_images(image: np.ndarray, width=8, height=8): def get_tile_images(image: np.ndarray, width=8, height=8):
_nrows, _ncols, depth = image.shape _nrows, _ncols, depth = image.shape
_strides = image.strides _strides = image.strides
@ -243,3 +244,30 @@ class LaMaInfillInvocation(BaseInvocation):
width=image_dto.width, width=image_dto.width,
height=image_dto.height, height=image_dto.height,
) )
@invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint")
class CV2InfillInvocation(BaseInvocation):
"""Infills transparent areas of an image using OpenCV Inpainting"""
image: ImageField = InputField(description="The image to infill")
def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.services.images.get_pil_image(self.image.image_name)
infilled = infill_cv2(image.copy())
image_dto = context.services.images.create(
image=infilled,
image_origin=ResourceOrigin.INTERNAL,
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
)
return ImageOutput(
image=ImageField(image_name=image_dto.image_name),
width=image_dto.width,
height=image_dto.height,
)

View File

@ -0,0 +1,20 @@
import cv2
import numpy as np
from PIL import Image
def cv2_inpaint(image: Image.Image) -> Image.Image:
# Prepare Image
image_array = np.array(image.convert("RGB"))
image_cv = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
# Prepare Mask From Alpha Channel
mask = image.split()[3].convert("RGB")
mask_array = np.array(mask)
mask_cv = cv2.cvtColor(mask_array, cv2.COLOR_BGR2GRAY)
mask_inv = cv2.bitwise_not(mask_cv)
# Inpaint Image
inpainted_result = cv2.inpaint(image_cv, mask_inv, 3, cv2.INPAINT_TELEA)
inpainted_image = Image.fromarray(cv2.cvtColor(inpainted_result, cv2.COLOR_BGR2RGB))
return inpainted_image

View File

@ -506,6 +506,14 @@ export const buildCanvasOutpaintGraph = (
}; };
} }
if (infillMethod === 'cv2') {
graph.nodes[INPAINT_INFILL] = {
type: 'infill_cv2',
id: INPAINT_INFILL,
is_intermediate: true,
};
}
if (infillMethod === 'tile') { if (infillMethod === 'tile') {
graph.nodes[INPAINT_INFILL] = { graph.nodes[INPAINT_INFILL] = {
type: 'infill_tile', type: 'infill_tile',

View File

@ -521,6 +521,14 @@ export const buildCanvasSDXLOutpaintGraph = (
}; };
} }
if (infillMethod === 'cv2') {
graph.nodes[INPAINT_INFILL] = {
type: 'infill_cv2',
id: INPAINT_INFILL,
is_intermediate: true,
};
}
if (infillMethod === 'tile') { if (infillMethod === 'tile') {
graph.nodes[INPAINT_INFILL] = { graph.nodes[INPAINT_INFILL] = {
type: 'infill_tile', type: 'infill_tile',

File diff suppressed because one or more lines are too long