OpenCV——Anime4k代码

 新的实时漫画放大算法。立志于改善AV画质。至于更清晰的说明请搜索baidu。

核心还是用到了8方向sobel核的思想,在改善边缘的同时,不造成振铃和过冲。

代码运行设置参数请详细见官网说明。

我的设置:"Art_0.png" "test_upscaled.png" 2 (Art_0.png直接下载测试有问题,估计不是真正的png图像,需要额外转一次格式)

值得一提的是,代码所需处理的图像必须是png格式(4通道,aRGB格式)

#include <iostream>
#include <opencv2\opencv.hpp>
#include <windows.h>
using namespace std;
using namespace cv;
/**
*
* @author bloc97
*/
class ImageKernel{

private:
	int width = 0;
	int height = 0;

	int * colorData;
	BYTE * derivData;
	int * tempColorData;
	BYTE * tempDerivData;
	Mat img;
	bool isForwardPass = true;

	int *runtype; 

public:
	ImageKernel()
	{
		runtype = new int[3];//Kernel run type, unblur strength (0-255), refine strength (0-255)
		runtype[0] = 0;
		runtype[1] = 80;
		runtype[2] = 225;
	}
	void setBufferedImage(Mat &img) {

		if (width != img.cols || height != img.rows) {
			colorData = (int *)img.data;
			derivData = new BYTE[img.rows * img.cols];
			tempColorData = new int[img.rows * img.cols];
			tempDerivData = new BYTE[img.rows * img.cols];
			
			width = img.cols;
			height = img.rows;
		}
		isForwardPass = true;
		this->img = img.clone();

	}

	void updateBufferedImage() {
		if (!isForwardPass) {
			runtype[0] = -1;
			this->execute(img.rows * img.cols);
			isForwardPass = !isForwardPass;
		}
	}
	void execute(int range)
	{
		for (int i = 0; i < range; i++)
		{
			this->run(i);
		}
	}
	void setPushStrength(float strength) {
		runtype[1] = clamp((int)(strength * 255), 0, 0xFFFF);
	}

	void setPushGradStrength(float strength) {
		runtype[2] = clamp((int)(strength * 255), 0, 0xFFFF);
	}

	void process() {
		//Compute Lum
		runtype[0] = 0;
		this->execute(img.rows * img.cols);

		int maxUnblur = runtype[1];
		int remainingUnblur = runtype[1];

		while (remainingUnblur > 0) { //While we still have to remove blur spread
			int currentThin = min(remainingUnblur, 0xFF);

			if (isForwardPass) {
				runtype[0] = 1;
			}
			else {
				runtype[0] = 2;
			}
			this->execute(img.rows * img.cols);
			isForwardPass = !isForwardPass;

			remainingUnblur -= currentThin;
		}

		runtype[1] = maxUnblur; //Restore original value

								//Compute Gradient
		if (isForwardPass) {
			runtype[0] = 3;
		}
		else {
			runtype[0] = 4;
		}
		this->execute(img.rows * img.cols);
		isForwardPass = !isForwardPass;

		int maxRefine = runtype[2];
		int remainingRefine = runtype[2];

		while (remainingRefine > 0) { //While we still have to refine
			int currentRefine = min(remainingRefine, 0xFF);

			if (isForwardPass) {
				runtype[0] = 5;
			}
			else {
				runtype[0] = 6;
			}
			this->execute(img.rows * img.cols);
			isForwardPass = !isForwardPass;

			remainingRefine -= currentRefine;
		}

		runtype[2] = maxRefine; //Restore original value



	}
private:
	int clamp(int i, int min, int max) {
		if (i < min) {
			i = min;
		}
		else if (i > max) {
			i = max;
		}

		return i;
	}
	int getAlpha(int argb) {
		return argb >> 24 & 0xFF;
	}
	int getRed(int rgb) {
		return rgb >> 16 & 0xFF;
	}
	int getGreen(int rgb) {
		return rgb >> 8 & 0xFF;
	}
	int getBlue(int rgb) {
		return rgb & 0xFF;
	}
	int toARGB(int a, int r, int g, int b) {
		return (clamp(a, 0, 0xFF) << 24) | (clamp(r, 0, 0xFF) << 16) | (clamp(g, 0, 0xFF) << 8) | (clamp(b, 0, 0xFF));
	}
	int getLuminance(int r, int g, int b) {
		return (r + r + g + g + g + b) / 6;
	}
	int unsignedByteToInt(byte b) {
		return ((int)b) & 0xFF;
	}

	bool compareLuminance3(int dark0, int dark1, int dark2, int light0, int light1, int light2) {
		return (dark0 < light0 && dark0 < light1 && dark0 < light2 && dark1 < light0 && dark1 < light1 && dark1 < light2 && dark2 < light0 && dark2 < light1 && dark2 < light2);
	}

	bool compareLuminance4(int dark0, int dark1, int dark2, int light0, int light1, int light2, int light3) {
		return (dark0 < light0 && dark0 < light1 && dark0 < light2 && dark0 < light3 && dark1 < light0 && dark1 < light1 && dark1 < light2&& dark1 < light3 && dark2 < light0 && dark2 < light1 && dark2 < light2 && dark2 < light3);
	}

	int averageGray(int d0, int d1, int d2) {
		return clamp((d0 + d1 + d2) / 3, 0, 0xFF);
	}

	int averageRGB(int c0, int c1, int c2) {
		int ra = (getRed(c0) + getRed(c1) + getRed(c2)) / 3;
		int ga = (getGreen(c0) + getGreen(c1) + getGreen(c2)) / 3;
		int ba = (getBlue(c0) + getBlue(c1) + getBlue(c2)) / 3;
		int aa = (getAlpha(c0) + getAlpha(c1) + getAlpha(c2)) / 3;

		return toARGB(aa, ra, ga, ba);
	}

	int weightedAverageGray(int d0, int d1, int alpha) {
		return clamp((d0 * (0xFF - alpha) + d1 * alpha) / 0xFF, 0, 0xFF);
	}

	int weightedAverageRGB(int c0, int c1, int alpha) {
		int ra = (getRed(c0) * (0xFF - alpha) + getRed(c1) * alpha) / 0xFF;
		int ga = (getGreen(c0) * (0xFF - alpha) + getGreen(c1) * alpha) / 0xFF;
		int ba = (getBlue(c0) * (0xFF - alpha) + getBlue(c1) * alpha) / 0xFF;
		int aa = (getAlpha(c0) * (0xFF - alpha) + getAlpha(c1) * alpha) / 0xFF;

		return toARGB(aa, ra, ga, ba);
	}

	void gradientRefine(int id, int strength, bool inversePass) {

		int * thisColorData = colorData;
		BYTE * thisDerivData = derivData;
		int * thisTempColorData = tempColorData;
		BYTE * thisTempDerivData = tempDerivData;

		if (inversePass) {
			thisColorData = tempColorData;
			thisDerivData = tempDerivData;
			thisTempColorData = colorData;
			thisTempDerivData = derivData;
		}

		strength = clamp(strength, 0, 0xFF);

		int x = id % width;
		int y = id / width;

		//Default translation constants
		int xn = -1;
		int xp = 1;
		int yn = -width;
		int yp = width;

		//If x or y is on the border, don't move out of bounds
		if (x == 0) {
			xn = 0;
		}
		else if (x == width - 1) {
			xp = 0;
		}
		if (y == 0) {
			yn = 0;
		}
		else if (y == height - 1) {
			yp = 0;
		}

		//Get indices for adjacent cells
		int ti = id + yn;
		int tli = ti + xn;
		int tri = ti + xp;

		int li = id + xn;
		int ri = id + xp;

		int bi = id + yp;
		int bli = bi + xn;
		int bri = bi + xp;

		int di0 = id;
		int di1 = id;
		int di2 = id;

		int li0 = id;
		int li1 = id;
		int li2 = id;
		int li3 = id;

		bool l4 = false;

		for (int kernelType = 0; kernelType<8; kernelType++) {
			if (kernelType == 0) { //Todo, binary search instead of searching entire list
				di0 = tli;
				di1 = ti;
				di2 = tri;

				li0 = id;
				li1 = bli;
				li2 = bi;
				li3 = bri;

				l4 = true;

			}
			else if (kernelType == 1) {
				di0 = ti;
				di1 = tri;
				di2 = ri;

				li0 = id;
				li1 = li;
				li2 = bi;

				l4 = false;

			}
			else if (kernelType == 2) {
				di0 = tri;
				di1 = ri;
				di2 = bri;

				li0 = id;
				li1 = tli;
				li2 = li;
				li3 = bli;

				l4 = true;

			}
			else if (kernelType == 3) {
				di0 = ri;
				di1 = bri;
				di2 = bi;

				li0 = id;
				li1 = ti;
				li2 = li;

				l4 = false;

			}
			else if (kernelType == 4) {
				di0 = bli;
				di1 = bi;
				di2 = bri;

				li0 = id;
				li1 = tli;
				li2 = ti;
				li3 = tri;

				l4 = true;

			}
			else if (kernelType == 5) {
				di0 = li;
				di1 = bli;
				di2 = bi;

				li0 = id;
				li1 = ti;
				li2 = ri;

				l4 = false;

			}
			else if (kernelType == 6) {
				di0 = tli;
				di1 = li;
				di2 = bli;

				li0 = id;
				li1 = tri;
				li2 = ri;
				li3 = bri;

				l4 = true;

			}
			else if (kernelType == 7) {
				di0 = tli;
				di1 = ti;
				di2 = li;

				li0 = id;
				li1 = bi;
				li2 = ri;

				l4 = false;

			}
			else if (kernelType < 0 || kernelType >= 8) {
				return;
			}

			if (l4) {

				int d0 = unsignedByteToInt(thisDerivData[di0]);
				int d1 = unsignedByteToInt(thisDerivData[di1]);
				int d2 = unsignedByteToInt(thisDerivData[di2]);

				int l0 = unsignedByteToInt(thisDerivData[li0]);
				int l1 = unsignedByteToInt(thisDerivData[li1]);
				int l2 = unsignedByteToInt(thisDerivData[li2]);
				int l3 = unsignedByteToInt(thisDerivData[li3]);

				if (compareLuminance4(d0, d1, d2, l0, l1, l2, l3)) {
					int c0 = thisColorData[di0];
					int c1 = thisColorData[di1];
					int c2 = thisColorData[di2];
					thisTempDerivData[id] = (byte)weightedAverageGray(thisDerivData[id], averageGray(d0, d1, d2), strength);
					thisTempColorData[id] = weightedAverageRGB(thisColorData[id], averageRGB(c0, c1, c2), strength);
					return;
				}
			}
			else {

				int d0 = unsignedByteToInt(thisDerivData[di0]);
				int d1 = unsignedByteToInt(thisDerivData[di1]);
				int d2 = unsignedByteToInt(thisDerivData[di2]);

				int l0 = unsignedByteToInt(thisDerivData[li0]);
				int l1 = unsignedByteToInt(thisDerivData[li1]);
				int l2 = unsignedByteToInt(thisDerivData[li2]);

				if (compareLuminance3(d0, d1, d2, l0, l1, l2)) {
					int c0 = thisColorData[di0];
					int c1 = thisColorData[di1];
					int c2 = thisColorData[di2];
					thisTempDerivData[id] = (byte)weightedAverageGray(thisDerivData[id], averageGray(d0, d1, d2), strength);
					thisTempColorData[id] = weightedAverageRGB(thisColorData[id], averageRGB(c0, c1, c2), strength);
					return;
				}
			}
		}
		thisTempDerivData[id] = thisDerivData[id];
		thisTempColorData[id] = thisColorData[id];


	}

	void unblur(int id, int strength, bool inversePass) {


		int * thisColorData = colorData;
		BYTE * thisDerivData = derivData;
		int * thisTempColorData = tempColorData;
		BYTE * thisTempDerivData = tempDerivData;

		if (inversePass) {
			thisColorData = tempColorData;
			thisDerivData = tempDerivData;
			thisTempColorData = colorData;
			thisTempDerivData = derivData;
		}

		strength = clamp(strength, 0, 0xFF);

		int x = id % width;
		int y = id / width;

		//Default translation constants
		int xn = -1;
		int xp = 1;
		int yn = -width;
		int yp = width;

		//If x or y is on the border, don't move out of bounds
		if (x == 0) {
			xn = 0;
		}
		else if (x == width - 1) {
			xp = 0;
		}
		if (y == 0) {
			yn = 0;
		}
		else if (y == height - 1) {
			yp = 0;
		}

		//Get indices for adjacent cells
		int ti = id + yn;
		int tli = ti + xn;
		int tri = ti + xp;

		int li = id + xn;
		int ri = id + xp;

		int bi = id + yp;
		int bli = bi + xn;
		int bri = bi + xp;

		int di0 = id;
		int di1 = id;
		int di2 = id;

		int li0 = id;
		int li1 = id;
		int li2 = id;
		int li3 = id;

		bool l4 = false;

		int lightestColor = thisColorData[id];
		int lightestLum = thisDerivData[id];

		for (int kernelType = 0; kernelType<8; kernelType++) {
			if (kernelType == 0) { //Todo, binary search instead of searching entire list
				di0 = tli;
				di1 = ti;
				di2 = tri;

				li0 = id;
				li1 = bli;
				li2 = bi;
				li3 = bri;

				l4 = true;

			}
			else if (kernelType == 1) {
				di0 = ti;
				di1 = tri;
				di2 = ri;

				li0 = id;
				li1 = li;
				li2 = bi;

				l4 = false;

			}
			else if (kernelType == 2) {
				di0 = tri;
				di1 = ri;
				di2 = bri;

				li0 = id;
				li1 = tli;
				li2 = li;
				li3 = bli;

				l4 = true;

			}
			else if (kernelType == 3) {
				di0 = ri;
				di1 = bri;
				di2 = bi;

				li0 = id;
				li1 = ti;
				li2 = li;

				l4 = false;

			}
			else if (kernelType == 4) {
				di0 = bli;
				di1 = bi;
				di2 = bri;

				li0 = id;
				li1 = tli;
				li2 = ti;
				li3 = tri;

				l4 = true;

			}
			else if (kernelType == 5) {
				di0 = li;
				di1 = bli;
				di2 = bi;

				li0 = id;
				li1 = ti;
				li2 = ri;

				l4 = false;

			}
			else if (kernelType == 6) {
				di0 = tli;
				di1 = li;
				di2 = bli;

				li0 = id;
				li1 = tri;
				li2 = ri;
				li3 = bri;

				l4 = true;

			}
			else if (kernelType == 7) {
				di0 = tli;
				di1 = ti;
				di2 = li;

				li0 = id;
				li1 = bi;
				li2 = ri;

				l4 = false;

			}
			else if (kernelType < 0 || kernelType >= 8) {
				return;
			}



			if (l4) {

				int d0 = unsignedByteToInt(thisDerivData[di0]);
				int d1 = unsignedByteToInt(thisDerivData[di1]);
				int d2 = unsignedByteToInt(thisDerivData[di2]);

				int l0 = unsignedByteToInt(thisDerivData[li0]);
				int l1 = unsignedByteToInt(thisDerivData[li1]);
				int l2 = unsignedByteToInt(thisDerivData[li2]);
				int l3 = unsignedByteToInt(thisDerivData[li3]);

				if (!compareLuminance4(d0, d1, d2, l0, l1, l2, l3)) {
					int c0 = thisColorData[di0];
					int c1 = thisColorData[di1];
					int c2 = thisColorData[di2];

					int newColor = weightedAverageRGB(thisColorData[id], averageRGB(c0, c1, c2), strength);
					int newLum = getLuminance(getRed(newColor), getGreen(newColor), getBlue(newColor));

					if (newLum > lightestLum) {
						lightestLum = newLum;
						lightestColor = newColor;
					}

				}
			}
			else {

				int d0 = unsignedByteToInt(thisDerivData[di0]);
				int d1 = unsignedByteToInt(thisDerivData[di1]);
				int d2 = unsignedByteToInt(thisDerivData[di2]);

				int l0 = unsignedByteToInt(thisDerivData[li0]);
				int l1 = unsignedByteToInt(thisDerivData[li1]);
				int l2 = unsignedByteToInt(thisDerivData[li2]);

				if (!compareLuminance3(d0, d1, d2, l0, l1, l2)) {
					int c0 = thisColorData[di0];
					int c1 = thisColorData[di1];
					int c2 = thisColorData[di2];

					int newColor = weightedAverageRGB(thisColorData[id], averageRGB(c0, c1, c2), strength);
					int newLum = getLuminance(getRed(newColor), getGreen(newColor), getBlue(newColor));

					if (newLum > lightestLum) {
						lightestLum = newLum;
						lightestColor = newColor;
					}
				}
			}
		}
		thisTempColorData[id] = lightestColor;
		thisTempDerivData[id] = (byte)clamp(lightestLum, 0, 0xFF);

	}

public:
		void computeGradient(int id, bool inversePass) {

		int * thisColorData = colorData;
		BYTE * thisDerivData = derivData;
		int * thisTempColorData = tempColorData;
		BYTE * thisTempDerivData = tempDerivData;

		if (inversePass) {
			thisColorData = tempColorData;
			thisDerivData = tempDerivData;
			thisTempColorData = colorData;
			thisTempDerivData = derivData;
		}

		int x = id % width;
		int y = id / width;

		//Default translation constants
		int xn = -1;
		int xp = 1;
		int yn = -width;
		int yp = width;

		//If x or y is on the border, don't move out of bounds
		if (x == 0) {
			xn = 0;
		}
		else if (x == width - 1) {
			xp = 0;
		}
		if (y == 0) {
			yn = 0;
		}
		else if (y == height - 1) {
			yp = 0;
		}

		//Get indices for adjacent cells
		int topi = id + yn;
		int topLefti = topi + xn;
		int topRighti = topi + xp;

		int lefti = id + xn;
		int righti = id + xp;

		int bottomi = id + yp;
		int bottomLefti = bottomi + xn;
		int bottomRighti = bottomi + xp;

		//Get values for adjacent cells
		int top = ((int)thisDerivData[topi]) & 0xFF;
		int topLeft = ((int)thisDerivData[topLefti]) & 0xFF;
		int topRight = ((int)thisDerivData[topRighti]) & 0xFF;

		int left = ((int)thisDerivData[lefti]) & 0xFF;
		int right = ((int)thisDerivData[righti]) & 0xFF;

		int bottom = ((int)thisDerivData[bottomi]) & 0xFF;
		int bottomLeft = ((int)thisDerivData[bottomLefti]) & 0xFF;
		int bottomRight = ((int)thisDerivData[bottomRighti]) & 0xFF;

		//Perform Sobel Operation
		int xSobel = abs(-topLeft + topRight
			- left - left + right + right
			- bottomLeft + bottomRight);
		int ySobel = abs(-topLeft - top - top - topRight
			+ bottomLeft + bottom + bottom + bottomRight);


		int deriv = clamp((xSobel + ySobel) / 2, 0, 0xFF);
		thisTempDerivData[id] = (byte)deriv;
		thisTempColorData[id] = thisColorData[id];
	}



		void run(int ID) {
		int id = ID;

		if (runtype[0] == 0) {

			int rgb = colorData[id];
			int r = getRed(rgb);
			int g = getGreen(rgb);
			int b = getBlue(rgb);
			int lum = clamp(getLuminance(r, g, b), 0, 0xFF);
			derivData[id] = (byte)lum;

		}
		else if (runtype[0] == 1) {
			unblur(id, runtype[1], false);
		}
		else if (runtype[0] == 2) {
			unblur(id, runtype[1], true);
		}
		else if (runtype[0] == 3) {
			computeGradient(id, false);
		}
		else if (runtype[0] == 4) {
			computeGradient(id, true);
		}
		else if (runtype[0] == 5) {
			gradientRefine(id, runtype[2], false);
		}
		else if (runtype[0] == 6) {
			gradientRefine(id, runtype[2], true);


		}
		else if (runtype[0] < 0) { //Prevent JVM from optimizing this chain of ifs into a switch statement, since switch/case causes Aparapi to crash
			derivData[id] = tempDerivData[id];
			colorData[id] = tempColorData[id];
		}

	}
};


/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/


/**
*
* @author bloc97
*/
void main(int argc, char *argv[])
{

	/**
	* @param argv the command line arguments
	*/

	ImageKernel kernel;

		
	if (argc < 3) {
		cout << "Error: Please specify input and output png files.";
		return;
	}

	String inputFile = argv[1];
	String outputFile = argv[2];
	
	Mat img = imread(inputFile, -1);
	imshow("img", img);
	waitKey();

	float scale = 2.0f;

	if (argc >= 4) {
		scale = atof(argv[3]);
	}
	scale = 2.0f;
	float pushStrength = scale / 6.0f;
	float pushGradStrength = scale / 2.0f;

	if (argc >= 5) {
		pushGradStrength = atof(argv[4]);
	}
	if (argc >= 6) {
		pushStrength = atof(argv[5]);
	}

	resize(img, img, Size(img.cols * scale, img.rows * scale), 0, 0, INTER_CUBIC);
	imshow("ddst", img);
	kernel.setPushStrength(pushStrength);
	kernel.setPushGradStrength(pushGradStrength);
	kernel.setBufferedImage(img);
	kernel.process();
	kernel.updateBufferedImage();
	Mat ddst;
	
	imshow("dst", img);
	waitKey();
	imwrite(outputFile, img);



}

原文是java的转成 c++难度不大,索性就赚了,将来也好用。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
告别AV画质:实时把动画变成4k高清,延时仅3毫秒,登上GitHub趋势榜 量子位 量子位 ​ 已认证的官方帐号 7,564 人赞同了该文章 栗子 发自 凹非寺 量子位 出品 | 公众号 QbitAI △ 来自《珈百璃的堕落》 看动画 (特别是里番) 的时候,总会觉得画质不够好,就算已经有1080p,还是会感到不够清晰。 所以,这个世界十分需要一套拯救分辨率的魔法。 如今,有个名叫Anime4K的开源算法,能在动画播放中,实时把画面变成4k,延时低至3毫秒。 能把720p/1080p变成2160p,也能把480p变成1080p。 团队说,这是当下最强 (State-of-the-Art) 的动画实时超分辨率方法,可以拿任何编程语言实现。 现在,项目已经在GitHub摘下3700多颗星,并一度登上了趋势榜。 那么,这个算法究竟是如何造福人类的? 只搞动画 团队在论文里感慨道: 传统超分辨率算法 (如Bicubic) ,结果不怎么好,因为它们根本不是为了动画而生的。 传统的去模糊 (Unblurring) 或锐化 (Sharpening) 方式,在靠近物体边缘的时候会发生过冲 (Overshoot) ,分散观众注意力,降低图像的感知质量 (Perceptual Quality) 。 而机器学习方法 (如waifu2x) 又太慢,完全不能实时 (<30毫秒) ,尤其是需要超高清的时候。 △ waifu2x 而Anime4K,只处理动画就够了,不考虑其他视频类型。这一点很重要。 动画没有真实视频那么多纹理 (Textures) ,基本都是用平直着色法 (Flat Shading) 处理的物体和线条。 只要画质变好一点点,观众也看得出。所以团队机智地想到,不用做整张的画质提升,专注于细化边缘就可以了,纹理之类的细节不重要。 具体怎样做,要从超分辨率的原理开始讲: 首先,一张图可以分为两部分: 一是低频分量,就是一张模糊的低分辨率图。二是高频残差,代表两种分辨率之间的差别 (Difference) 。 输入一张低清图,把它变成一个更低清的版本,就能得出一个残差。 把残差变薄 (Thin) 、锐化 (Sharpen) ,再加到低清图上,就能得到一张高清图。 但残差稍稍有点错误,就会造成振铃和过冲,影响效果。这也是前辈的缺陷所在。 于是,团队找到了一种新方法: 首先把残差厚度最小化当做目标,这个没有问题。 但直接把随意变换(Arbitrarily Transformed)得到的残差,用到一张低清图上是不行的。低清图要做出相应改变,才能与残差和平相处,得出理想的超分辨率结果。 所以,当输入一张图和它的残差之后,“push”残差的像素,让残差线变细; 同时,每做一个push,都要在彩色的低清图上,执行一个相同的操作。 这样,既能把模糊最小化,也不会出现振铃和过冲,这两个降低画质的现象。 比一比吧 这场比赛,Anime4K (最右) 的对手有:来自madVR的不开源算法NGU前辈 (左二) ,以及开源的机器学习算法waifu2x前辈 (左三) 。 第一题,眼睛: 第二题,耳朵: 第三题,玉手: 第四题,全脸: waifu2x前辈的效果,明显不及Anime4K,常见虚影。速度也有明显缺陷,每张图耗时超过1秒。 NGU前辈生成的画质,与Anime4K相近,但也常常被Anime4K打败。 不止如此,NGU每张耗时~6毫秒,Anime4K只要~3毫秒,快了一倍,更加适应实时生成的需求了。 效果相近的话,为啥不直接用NGU?因为不开源。 如果,你觉得720p/1080p的动画,没必要变成4K这么奢侈,那还可以把480p拯救到1080p啊: 依然,Anime4K和没开源的NGU不相上下。 最后,尽管已经获得了精湛的画质提升技能,团队也没有就此抛弃机器学习的力量。 因为在拯救静止画作 (而非动画) 的时候,Anime4K的短板显现了。这时候,让机器学习选手waifu2x和它并肩作战,更加成功一些: 需要实时给动画提升分辨率,还是只用Anime4K吧。 反正也已经开源了。 项目传送门: bloc97/Anime4K ​ github.com 图标 论文传送门: https://github.com/bloc97/Anime4K/blob/master/Preprint.md ​ github.com — 完 —
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值