From d3780188967acd3059c6d46b9dc29fdc89c5ae4c Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 24 May 2023 10:23:08 +0200 Subject: [PATCH] add new upscaler test --- bin/handler.py | 6 +- bin/lib/__pycache__/edi.cpython-311.pyc | Bin 0 -> 9016 bytes bin/lib/edi.py | 154 ++++++++++++++++++++++++ bin/lib/test.py | 15 +++ imagevideoupscaler-cli.py | 8 +- 5 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 bin/lib/__pycache__/edi.cpython-311.pyc create mode 100644 bin/lib/edi.py create mode 100644 bin/lib/test.py diff --git a/bin/handler.py b/bin/handler.py index 1079026..a452984 100644 --- a/bin/handler.py +++ b/bin/handler.py @@ -314,7 +314,7 @@ class Handler: self.command_list.append( ( self.files, fsrpath, quality_setting, i, self.maxlength, self.os_type ) ) self.pool = multiprocessing.Pool( self.threads ) - if ( mode == 'NN' ): + if ( mode == 'B' ): self.pool.starmap( bilinearEngine, self.command_list ); elif ( mode == 'fsr' ): self.pool.starmap( upscalerEngine, self.command_list ); @@ -447,7 +447,9 @@ def upscalerEngine ( files, fsrpath, quality_setting, number, maxlength, os_type print( '\n\nCompleted executing Job\n\n\n PROCESS: ', number, '\n\n\n' ); -def bilinearEngine ( files, fsrpath, quality_setting, number, maxlength, os_type ): +def bilinearEngine ( files, fsrpath, quality_setting, number, maxlength, os_type, version ): + if ( version == 'HQC' ): + scaler = 'HighQualityCubic' files = files; # Refactoring of commands that are longer than 32K characters fileout = []; diff --git a/bin/lib/__pycache__/edi.cpython-311.pyc b/bin/lib/__pycache__/edi.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8cdd1beadb4dc298fb604af7d38ce6a68570c05 GIT binary patch literal 9016 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 4. + # s is the scaling factor, support any s > 0 (e.g. use s=2 to upscale by 2, use s=0.5 to downscale by 2) + +If you want to directly call EDI_upscale to upscale image by the scale of 2: + + EDI_upscale(img, m) + + # m should be the power of 2. Will increment by 1 if input m is odd + +If you want to directly call EDI_downscale to downscale image by the scale of 2: + + EDI_downscale(img) + +""" + +def EDI_downscale(img): + + # initializing downgraded image + w, h = img.shape + imgo2 = np.zeros((w//2, h//2)) + + # downgrading image + for i in range(w//2): + for j in range(h//2): + imgo2[i][j] = int(img[2*i][2*j]) + + return imgo2.astype(img.dtype) + +def EDI_upscale(img, m): + + # m should be equal to a power of 2 + if m%2 != 0: + m += 1 + + # initializing image to be predicted + w, h = img.shape + imgo = np.zeros((w*2,h*2)) + + # Place low-resolution pixels + for i in range(w): + for j in range(h): + imgo[2*i][2*j] = img[i][j] + + y = np.zeros((m**2,1)) # pixels in the window + C = np.zeros((m**2,4)) # interpolation neighbours of each pixel in the window + + # Reconstruct the points with the form of (2*i+1,2*j+1) + for i in range(math.floor(m/2), w-math.floor(m/2)): + for j in range(math.floor(m/2), h-math.floor(m/2)): + tmp = 0 + for ii in range(i-math.floor(m/2), i+math.floor(m/2)): + for jj in range(j-math.floor(m/2), j+math.floor(m/2)): + y[tmp][0] = imgo[2*ii][2*jj] + C[tmp][0] = imgo[2*ii-2][2*jj-2] + C[tmp][1] = imgo[2*ii+2][2*jj-2] + C[tmp][2] = imgo[2*ii+2][2*jj+2] + C[tmp][3] = imgo[2*ii-2][2*jj+2] + tmp += 1 + + # calculating weights + # a = (C^T * C)^(-1) * (C^T * y) = (C^T * C) \ (C^T * y) + a = np.matmul(np.matmul(np.linalg.pinv(np.matmul(np.transpose(C),C)), np.transpose(C)), y) + imgo[2*i+1][2*j+1] = np.matmul([imgo[2*i][2*j], imgo[2*i+2][2*j], imgo[2*i+2][2*j+2], imgo[2*i][2*j+2]], a) + + # Reconstructed the points with the forms of (2*i+1,2*j) and (2*i,2*j+1) + for i in range(math.floor(m/2), w-math.floor(m/2)): + for j in range(math.floor(m/2), h-math.floor(m/2)): + tmp = 0 + for ii in range(i-math.floor(m/2), i+math.floor(m/2)): + for jj in range(j-math.floor(m/2), j+math.floor(m/2)): + y[tmp][0] = imgo[2*ii+1][2*jj-1] + C[tmp][0] = imgo[2*ii-1][2*jj-1] + C[tmp][1] = imgo[2*ii+1][2*jj-3] + C[tmp][2] = imgo[2*ii+3][2*jj-1] + C[tmp][3] = imgo[2*ii+1][2*jj+1] + tmp += 1 + + # calculating weights + # a = (C^T * C)^(-1) * (C^T * y) = (C^T * C) \ (C^T * y) + a = np.matmul(np.matmul(np.linalg.pinv(np.matmul(np.transpose(C),C)), np.transpose(C)), y) + imgo[2*i+1][2*j] = np.matmul([imgo[2*i][2*j], imgo[2*i+1][2*j-1], imgo[2*i+2][2*j], imgo[2*i+1][2*j+1]], a) + imgo[2*i][2*j+1] = np.matmul([imgo[2*i-1][2*j+1], imgo[2*i][2*j], imgo[2*i+1][2*j+1], imgo[2*i][2*j+2]], a) + + # Fill the rest with bilinear interpolation + np.clip(imgo, 0, 255.0, out=imgo) + imgo_bilinear = cv2.resize(img, dsize=(h*2,w*2), interpolation=cv2.INTER_LINEAR) + imgo[imgo==0] = imgo_bilinear[imgo==0] + + return imgo.astype(img.dtype) + +def EDI_predict(img, m, s): + + try: + w, h = img.shape + except: + sys.exit("Error input: Please input a valid grayscale image!") + + output_type = img.dtype + + if s <= 0: + sys.exit("Error input: Please input s > 0!") + + elif s == 1: + print("No need to rescale since s = 1") + return img + + elif s < 1: + # Calculate how many times to do the EDI downscaling + n = math.floor(math.log(1/s, 2)) + + # Downscale to the expected size with linear interpolation + linear_factor = 1/s / math.pow(2, n) + if linear_factor != 1: + img = cv2.resize(img, dsize=(int(h/linear_factor),int(w/linear_factor)), interpolation=cv2.INTER_LINEAR).astype(output_type) + + for i in range(n): + img = EDI_downscale(img) + return img + + elif s < 2: + # Linear Interpolation is enough for upscaling not over 2 + return cv2.resize(img, dsize=(int(h*s),int(w*s)), interpolation=cv2.INTER_LINEAR).astype(output_type) + + else: + # Calculate how many times to do the EDI upscaling + n = math.floor(math.log(s, 2)) + for i in range(n): + img = EDI_upscale(img, m) + + # Upscale to the expected size with linear interpolation + linear_factor = s / math.pow(2, n) + if linear_factor == 1: + return img.astype(output_type) + + # Update new shape + w, h = img.shape + return cv2.resize(img, dsize=(int(h*linear_factor),int(w*linear_factor)), interpolation=cv2.INTER_LINEAR).astype(output_type) + diff --git a/bin/lib/test.py b/bin/lib/test.py new file mode 100644 index 0000000..7f09c8f --- /dev/null +++ b/bin/lib/test.py @@ -0,0 +1,15 @@ +import edi +import numpy as np +import argparse + +ap = argparse.ArgumentParser( description='Testing for edi. NOTE: No error catching!' ) +ap.add_argument( 'inputfile', help='Input file for upscaling' ) +ap.add_argument( 'outputfile', help='Output file' ) +ap.add_argument( '-S', '--scalefactor', help='Scale factor' ) +ap.add_argument( '-a', '--sampling', help='Sampling window size. The bigger, the blurrier. Best >= 4') +ap.set_defaults( sampling=4 ) +ap.set_defaults( scalefactor=2 ) + +args = ap.parse_args() + +print( edi.EDI_predict( np.load( args.inputfile, allow_pickle=True ), args.sampling, args.scalefactor ) ) \ No newline at end of file diff --git a/imagevideoupscaler-cli.py b/imagevideoupscaler-cli.py index 8631a9d..d285aeb 100644 --- a/imagevideoupscaler-cli.py +++ b/imagevideoupscaler-cli.py @@ -15,17 +15,17 @@ import multiprocessing allowedFiletypes = [ 'png', 'jpg' ]; if __name__ == '__main__': - ap = argparse.ArgumentParser( description='FSRImageVideoUpscaler - CLI, a CLI application to upscale videos and images using FSR.' ) + ap = argparse.ArgumentParser( description='ImageVideoUpscaler - CLI, a CLI application to upscale videos and images using FSR.' ) ap.add_argument( 'inputfile', help='File path for the video / image to be upscaled' ) ap.add_argument( 'outputfile', help='File path for the video / image that was upscaled' ) ap.add_argument( '-s', '--scalefactor', help='Scale factor for the video / image. Can be a integer from 1 - 4' ) - ap.add_argument( '-F', '--filetype', help='Change the file type of the temporary image files. Supports png, jpg. Video quality: png > jpg. Png is default, if not specified.' ) + ap.add_argument( '-F', '--filetype', help='Change the file type of the temporary image files. Supports png, jpg. Video quality: png > jpg. PNG is default, if not specified.' ) ap.add_argument( '-S', '--sharpening', help='Sharpening factor (between 0 and 1 whereas 0 means no sharpening, 1 the most sharpening. Recommendation: Do not exceed 0.25, as it often looks bad)' ) - ap.add_argument( '-N', '--noscaling', help='Do not upscale video, instead only sharpen. Sharpening argument required!', action='store_true' ) ap.add_argument( '-t', '--threading', help='Use special threading mode with SS scaler (spawns 16 threads upscaling at one time)', action='store_true' ) ap.add_argument( '-T', '--threads', help='Thread count to use. Cannot exceed CPU thread count. Scaling non-linear (using 2 threads is not exactly 2x the speed of 1 thread). Scales well with FSR, barely with Real-ESRGAN, as it uses mostly the GPU to upscale' ) - ap.add_argument( '-E', '--engine', help='Upscaling engine. Can be fsr, NN (for NearestNeighbor) or SS (for Real-ESRGAN). FSR tends to be lower quality, but faster, NeirestNeighbour is super fast but very ugly, Real-ESRGAN is meant for anime and is super slow. Defaults to fsr' ) + ap.add_argument( '-E', '--engine', help='Upscaling engine. Can be fsr, C (for Cubic), HQC (for HighQuality Cubic) or SS (for Real-ESRGAN). FSR tends to be higher, Cubic is quite fast but quite low quality, HighQualityCubic is of higher quality, but slower. Real-ESRGAN is meant for anime and is super slow. Defaults to fsr' ) ap.add_argument( '-M', '--model', help='Only available if using Real-ESRGAN. Change the ML-Model used to upsample video, can be: realesr-animevideov3 | realesrgan-x4plus-anime , defaults to realesr-animevideov3' ) + ap.add_argument( '-N', '--noscaling', help='Do not upscale video, instead only sharpen. Sharpening argument required!', action='store_true' ) args = ap.parse_args() handler = bin.handler.Handler()