个人感觉tensorflow的resize_image方法有个大坑,这里只是以双三次插值为例对图片进行缩放为例,对比opencv以及PIL算法的不同。之所以说tensorflow的插值算法有些坑,是因为他的resize算法,无论怎么配置,都无法和openc以及pil库提供的插值算法进行对齐,常用的视频编解码工具ffmpeg自带的双三次插值接近PIL效果。另外,三者的结果都不相同,opencv个人感觉最好,图像显得锐化一些;tensorflow也是有锐化效果,但是部分细节丢失严重,更加接近于最近邻插值算法。

由于tensorflow在1.0版本是静态图的形式,自定义插值算法变得不是很容易。在分析完三者之间的不同后,这里简单提供一个自定义tensorflow的resize_image方法,效果和opencv是一样的。

具体程序:

因为涉及到部分功能代码重复,我们分为三部分:一,读取yuv数据信息,包括帧数,长宽等。二,resize_image,对图像进行插值缩放。三,保存结果。

一,数据读取:

def getFrameNUm(filename, bitdepth, W, H):bytesPerPixel = math.ceil(bitdepth / 8)framePixels = bytesPerPixel * H * W * 3 // 2fp = open(filename, 'rb')fp.seek(0, 2)maxFrameNum = fp.tell()maxFrameNum = maxFrameNum // framePixelsreturn maxFrameNumdef readyuv420_byframe(filename, bitdepth, W, H, startframe, totalframe, show=False):bytesPerPixel = math.ceil(bitdepth / 8)seekPixels = startframe * H * W * 3 // 2fp = open(filename, 'rb')fp.seek(bytesPerPixel * seekPixels)for i in range(totalframe):Y = fp.read(H * W * bytesPerPixel)U = fp.read(H // 2 * W // 2 * bytesPerPixel)V = fp.read(H // 2 * W // 2 * bytesPerPixel)Y = np.reshape(np.fromstring(Y, dtype=np.uint8), (H, W))U = np.reshape(np.fromstring(U, dtype=np.uint8), (H // 2, W // 2))V = np.reshape(np.fromstring(V, dtype=np.uint8), (H // 2, W // 2))yield Y, U, V# 具体读取部分sdr_video = '/360X360_8bit_sor_cv_down.yuv'save_hdr_video = '720X720_8bit_sor_cv_down_up.yuv'sdrFrameNum = getFrameNUm(sdr_video, 8, 360, 360)sdrF = readyuv420_byframe(sdr_video, 8, 360, 360, 0, sdrFrameNum)width = 720height = 720dim_y_shape = (width, height)dim_uv_shape = (width // 2, height // 2)

插值算法:

# PIL部分,from PIL import Imagefor index in range(sdrFrameNum):sdrY, sdrU, sdrV = sdrF.__next__()img_y = Image.fromarray(sdrY)img_y = img_y.resize(dim_y_shape, Image.BICUBIC)img_y = np.array(img_y)img_u = Image.fromarray(sdrU)img_u = img_u.resize(dim_uv_shape, Image.BICUBIC)img_u = np.array(img_u)img_v = Image.fromarray(sdrV)img_v = img_v.resize(dim_uv_shape, Image.BICUBIC)img_v = np.array(img_v)#  opencv部分,import cv2for index in range(sdrFrameNum):sdrY, sdrU, sdrV = sdrF.__next__()resize_img_y = cv2.resize(sdrY, dim_y_shape, interpolation=cv2.INTER_CUBIC)resize_img_u = cv2.resize(sdrU, dim_uv_shape, interpolation=cv2.INTER_CUBIC)resize_img_v = cv2.resize(sdrV, dim_uv_shape, interpolation=cv2.INTER_CUBIC)tensorflow部分hdr_y = tf.placeholder(tf.float32, [None, None, None, 1], name='hdy_img')hdr_u = tf.placeholder(tf.float32, [None, None, None, 1], name='hdu_img')hdr_v = tf.placeholder(tf.float32, [None, None, None, 1], name='hdv_img')input_y_s = tf.image.resize_image_with_pad(hdr_y, des_h, des_w, method=2)input_u_s = tf.image.resize_image_with_pad(hdr_u, des_h//2, des_w//2, method=2)input_v_s = tf.image.resize_image_with_pad(hdr_v, des_h//2, des_w//2, method=2)# input_y_s = tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=False)# input_u_s = tf.image.resize_bicubic(hdr_u, [des_h//2, des_w//2], align_corners=False)# input_v_s = tf.image.resize_bicubic(hdr_v, [des_h//2, des_w//2], align_corners=False)result_y = tf.clip_by_value(input_y_s, 0.0, 1.0)result_u = tf.clip_by_value(input_u_s, 0.0, 1.0)result_v = tf.clip_by_value(input_v_s, 0.0, 1.0)with tf.Session(config=tf.ConfigProto(log_device_placement=False)) as sess:for step in range(int(total_frame)):sdrY_ori, sdrU, sdrV = sdrF.__next__()sdrY = np.reshape(sdrY_ori, (1, src_h, src_w, 1))sdrU = np.reshape(sdrU, (1, src_h // 2, src_w // 2, 1))sdrV = np.reshape(sdrV, (1, src_h // 2, src_w // 2, 1))hy_value, hu_value, hv_value = sdrY / 255.0, sdrU / 255.0, sdrV / 255.0feed_dict = {hdr_y: hy_value, hdr_u: hu_value, hdr_v: hv_value}hdr_y_r, hdr_u_r, hdr_v_r = sess.run([result_y, result_u, result_v], feed_dict=feed_dict)hdrY_pred_img = np.around(hdr_y_r[0, :, :, 0] * 255.0).astype(np.uint8)hdrU_pred_img = np.around(hdr_u_r[0, :, :, 0] * 255.0).astype(np.uint8)hdrV_pred_img = np.around(hdr_v_r[0, :, :, 0] * 255.0).astype(np.uint8)

数据保存:

writeyuv420p(save_pre_video, des_w, des_h, hdrY_pred_img, hdrU_pred_img, hdrV_pred_img)def writeyuv420p(filename, W, H, Y, U, V):fp = open(filename, 'wb')fp.write(Y)fp.write(U)fp.write(V)fp.close()def writeyuv420p_add(filename, W, H, Y, U, V):fp = open(filename, 'ab+')fp.write(Y)fp.write(U)fp.write(V)fp.close()

tensorflow有三种插值算法:

一:# 2代表双三次插值 bicubic
tf.image.resize_image_with_pad(hdr_y, des_h, des_w, method=2)
二:
tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=False)
三:
tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=True)

结论

这里值讨论由于插值而产生的移位情况。这里以opencv和PIL的结果作为标准(不看效果,只看是否移位),为何以这两个为标准?主要是因为他的效果和ffmpeg的结果一致,没有发生移位情况。

一:下采样插值

对于tensorflow下采样,

tf.image.resize_image_with_pad(hdr_y, des_h, des_w, method=2)
tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=False)

这两种设置方法,得出的结果是一样的。但是相较于opencv和PIL(以后只以opencv为例),整体像右下角偏移一个像素左右。

相比于:

tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=True)

这种设置方法相比于opencv,是往中间进行偏移。直观看,就是四个角或边都往中心方向移位。

这是下采样的结论。

对于上采样,要对比的就比较多了,主要是上采样回去以后,是要和groundtruth相比,也就是和源图像比是否产生了移位。那么对于待采样图像,他是由原图像进行下采样得到,下采样是通过opencv和tensorflow的三种方法得到的结果(除去一个相同的)。

opencv下采样结果,进行上采样

首先对于opencv下采样后,用三种方法进行上采样。采用opencv上采样插值后,没有发生移位(这个我们之前就提到过)

对于一下这两种方法,他们两个结果一样

tf.image.resize_image_with_pad(hdr_y, des_h, des_w, method=2)
tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=False)

tensorflow的这两种方法,相对于groundtruth,是有一个左上角的偏移。

在这里你可能也想到了,既然上面的结论,下采样会右下,上采样会左上,那么:

tf.image.resize_bicubic(hdr_y, [des_h, des_w], align_corners=False)

tensorflow的是向四周进行移位的。

几种组合方式的上下采样其实也可以猜想一下了。因为tensorflow的下采样,要么向左下角偏移,要么向中心偏移;上采样偏移正好相反。

为什么会出现这种情况?这是因为在插值过程中需要确定用到原图中的哪几个点,而确定方法有所不同。

目标图像的每一个像素点都需要原图中几个点的信息。需要原图像的那几个点应当首先确定:目标图像的像素点在原图像的哪个位置。

   srcX=dstX* (srcWidth/dstWidth)  srcY = dstY * (srcHeight/dstHeight) 中心对齐(OpenCV也是如此): SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5 SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5
# 其中,dstX 为rang(0,dstWidth),detY为rang(0,desHeight)

由上面两种公式来进行确定,也就是因为这两种不同的确定方法而导致的位置偏移。其部分详细解释可参考该篇文章

双三次插值

双三次插值的具体算法可以参考这里,主要解释一下这里的权重公式

这里的x是目标图像的某像素点距离原图像参考像素点的位置(16个哦)。并且,每一个还分为x和y两个分量。

这里将双线性插值和双三次插值的Python代码列出,结果比C版本的要慢很多。具体原因待查。并且这里主要是为了理解这些插值的算法公式原理,在边缘方面没有做详细处理。

# 最近邻
def NN_interpolation(img,dstH,dstW):scrH,scrW,_=img.shaperetimg=np.zeros((dstH,dstW,3),dtype=np.uint8)for i in range(dstH):for j in range(dstW):scrx=round((i+1)*(scrH/dstH))scry=round((j+1)*(scrW/dstW))retimg[i,j]=img[scrx-1,scry-1]return retimg
#双线性
def BiLinear_interpolation(img,dstH,dstW):scrH,scrW,_=img.shapeimg=np.pad(img,((0,1),(0,1),(0,0)),'constant')retimg=np.zeros((dstH,dstW,3),dtype=np.uint8)for i in range(dstH):for j in range(dstW):scrx=(i+1)*(scrH/dstH)-1scry=(j+1)*(scrW/dstW)-1x=math.floor(scrx)y=math.floor(scry)u=scrx-xv=scry-yretimg[i,j]=(1-u)*(1-v)*img[x,y]+u*(1-v)*img[x+1,y]+(1-u)*v*img[x,y+1]+u*v*img[x+1,y+1]return retimgdef BiBubic(x):x=abs(x)if x<=1:return 1-2.5*(x**2)+(x**3)*1.5elif x<2:return 2-4*x+2.5*(x**2)-(x**3)*0.5else:return 0
#双三次
def BiCubic_interpolation(img,dstH,dstW):scrH,scrW,_=img.shaperetimg=np.zeros((dstH,dstW,3),dtype=np.uint8)for i in range(dstH):print('one pixel:',i,'total:',dstH)for j in range(dstW):scrx=(i+0.5)*(scrH/dstH) - 0.5scry=(j+0.5)*(scrW/dstW) - 0.5x=math.floor(scrx)y=math.floor(scry)u=scrx-xv=scry-ytmp=0for ii in range(-1,3):for jj in range(-1,3):if x+ii<0 or y+jj<0 or x+ii>=scrH or y+jj>=scrW:continuetmp+=img[x+ii,y+jj]*BiBubic(ii-u)*BiBubic(jj-v)retimg[i,j]=np.clip(tmp,0,255)return retimg

INTER_AREA

area插值算法简单说一下其原理,引自这里

总之:

缩图的时候相当于均值滤波,如果目标缩放倍数不是整数,那么通过举例来算权重:

如果是放大的话,接近于双线性插值算法。这个在opencv官方介绍中有这么一句话

when the image is zoomed, it is similar to the INTER_NEAREST method

自定义tensorflow的双三次插值

这里只能选择上采样两倍的固定倍率,单通道正方形的灰度图,并采用了卷积的方式进行实现。这是因为一个原点变成四个目标像素点,并且距离不会变化。

def caculate_bic_weight():x = [1.75, 0.75, 0.25, 1.25]y = [1.75, 0.75, 0.25, 1.25]# x = [1.25, 0.25, 0.75, 1.75]# y = [1.75, 0.75, 0.25, 1.25]# x = [1.75, 0.75, 0.25, 1.25]# y = [1.25, 0.25, 0.75, 1.75]# x = [1.25, 0.25, 0.75, 1.75]# y = [1.25, 0.25, 0.75, 1.75]total = 0for x_idx in range(4):for y_idx in range(4):# print("按列排列")x_weight = bicubic_kernel(x[x_idx])y_weight = bicubic_kernel(y[y_idx])xy_weight = x_weight*y_weightprint(xy_weight)total += xy_weightprint(total)

这里四组x,y,相当于u、v的值,两个权重相乘得到对应源图像上像素的权重。如果确定的目标图像像素在原图像像素的位置就会发现,一个源图像周围分布四个目标像素。四组x,y分别表示左上,右上,左下,右下,在高宽上的权重分量。

def cus_bicubic_center_tf_up_X2(sdr_video,save_hdr_video,mulit_channel=True):if mulit_channel:img_y = Image.open(sdr_video).convert('RGB')img = tf.placeholder(tf.float32, [None, None, None, 3], name='hdy_img')img_data = np.array(img_y)img_data = np.expand_dims(img_data, axis=0)else:img_y = Image.open(sdr_video).convert('L')img = tf.placeholder(tf.float32, [None, None, None, 1], name='hdy_img')img_data = np.array(img_y)img_data = np.expand_dims(img_data, axis=-1)img_data = np.expand_dims(img_data, axis=0)pad1 = np.array([[0, 0], [2, 1], [2, 1], [0, 0]])pad2 = np.array([[0, 0], [2, 1], [1, 2], [0, 0]])pad3 = np.array([[0, 0], [1, 2], [2, 1], [0, 0]])pad4 = np.array([[0, 0], [1, 2], [1, 2], [0, 0]])filter_bic_1 = tf.constant([[[[0.00054931640625, -0.00531005859375, -0.02032470703125, 0.00164794921875],[-0.00531005859375, 0.05133056640625, 0.19647216796875, -0.01593017578125],[-0.02032470703125, 0.19647216796875, 0.75201416015625, -0.06097412109375],[0.00164794921875, -0.01593017578125, -0.06097412109375, 0.00494384765625]]]])filter_bic_2 = tf.constant([[[[0.00164794921875, -0.02032470703125, -0.00531005859375, 0.00054931640625],[-0.01593017578125, 0.19647216796875, 0.05133056640625, -0.00531005859375],[-0.06097412109375, 0.75201416015625, 0.19647216796875, -0.02032470703125],[0.00494384765625, -0.06097412109375, -0.01593017578125, 0.00164794921875]]]])filter_bic_3 = tf.constant([[[[0.00164794921875, -0.01593017578125, -0.06097412109375, 0.00494384765625],[-0.02032470703125, 0.19647216796875, 0.75201416015625, -0.06097412109375],[-0.00531005859375, 0.05133056640625, 0.19647216796875, -0.01593017578125],[0.00054931640625, -0.00531005859375, -0.02032470703125, 0.00164794921875]]]])filter_bic_4 = tf.constant([[[[0.00494384765625, -0.06097412109375, -0.01593017578125, 0.00164794921875],[-0.06097412109375, 0.75201416015625, 0.19647216796875, -0.02032470703125],[-0.01593017578125, 0.19647216796875, 0.05133056640625, -0.00531005859375],[0.00164794921875, -0.02032470703125, -0.00531005859375, 0.00054931640625]]]])filter_bic_1 = tf.reshape(filter_bic_1, [4, 4, 1, 1])filter_bic_2 = tf.reshape(filter_bic_2, [4, 4, 1, 1])filter_bic_3 = tf.reshape(filter_bic_3, [4, 4, 1, 1])filter_bic_4 = tf.reshape(filter_bic_4, [4, 4, 1, 1])img_pad_1 = tf.pad(img, pad1, mode='SYMMETRIC',  name='pad_hdrx1')img_pad_2 = tf.pad(img, pad2, mode='SYMMETRIC',  name='pad_hdrx2')img_pad_3 = tf.pad(img, pad3, mode='SYMMETRIC', name='pad_hdry1')img_pad_4 = tf.pad(img, pad4, mode='SYMMETRIC', name='pad_hdry2')if mulit_channel:filter_bic_mui_1 = tf.concat([filter_bic_1, filter_bic_1, filter_bic_1], axis=2)filter_bic_mui_2 = tf.concat([filter_bic_2, filter_bic_2, filter_bic_2], axis=2)filter_bic_mui_3 = tf.concat([filter_bic_3, filter_bic_3, filter_bic_3], axis=2)filter_bic_mui_4 = tf.concat([filter_bic_4, filter_bic_4, filter_bic_4], axis=2)hdr_pad_1 = tf.nn.depthwise_conv2d(img_pad_1, filter=filter_bic_mui_1, strides=[1, 1, 1, 1], padding='VALID')hdr_pad_2 = tf.nn.depthwise_conv2d(img_pad_2, filter=filter_bic_mui_2, strides=[1, 1, 1, 1], padding='VALID')# axis yhdr_pad_3 = tf.nn.depthwise_conv2d(img_pad_3, filter=filter_bic_mui_3, strides=[1, 1, 1, 1], padding='VALID')hdr_pad_4 = tf.nn.depthwise_conv2d(img_pad_4, filter=filter_bic_mui_4, strides=[1, 1, 1, 1], padding='VALID')else:hdr_pad_1 = tf.nn.conv2d(img_pad_1, filter=filter_bic_1, strides=[1, 1, 1, 1], padding='VALID')hdr_pad_2 = tf.nn.conv2d(img_pad_2, filter=filter_bic_2, strides=[1, 1, 1, 1], padding='VALID')hdr_pad_3 = tf.nn.conv2d(img_pad_3, filter=filter_bic_3, strides=[1, 1, 1, 1], padding='VALID')hdr_pad_4 = tf.nn.conv2d(img_pad_4, filter=filter_bic_4, strides=[1, 1, 1, 1], padding='VALID')hdr_result = tf.concat([hdr_pad_1, hdr_pad_2, hdr_pad_3, hdr_pad_4],axis=-1)hdr_result = tf.depth_to_space(hdr_result, 2)# hdr_result = SubpixelConv2d(hdr_result, 2)hdr_result = tf.clip_by_value(hdr_result, 0.0, 1.0)with tf.Session() as sess:sess.run(tf.initialize_all_variables())feed_dict = {img: img_data/255.0}hdr_result_re = sess.run(hdr_result,feed_dict= feed_dict)hdr_result_re = hdr_result_re[0]*255.0if mulit_channel:hdr_result_re = hdr_result_re.astype(np.uint8)hdr_result_re = cv2.cvtColor(hdr_result_re, cv2.COLOR_RGB2BGR)cv2.imwrite(save_hdr_video,hdr_result_re)

将四个点堆放成四个通道,然后通过subpixel操作。tf.depth_to_space,可以也可以通过该方法实现:

def SubpixelConv2d(inputs, scale=2, n_out_channel=1):shape_img = tf.shape(inputs)y = tf.reshape(inputs, [-1, shape_img[2], scale, scale * n_out_channel])y = tf.transpose(y, [0, 2, 1, 3])y = tf.reshape(y, [-1, shape_img[1] * scale, shape_img[2] * scale, n_out_channel])return y

这里的tf的padding方式也挺有讲究。不过只要理解了双三次插值的具体方法以及边界处理方式,还是挺好想象的。

这里只是单通道的灰度图,如果想采用多通道的可以将filter_bic通过concat进行连接,并且使用

tf.nn.depthwise_conv2d()

总结:

这种只能固定整数倍缩放才能够实现通过卷积的方式进行上下采样。这是因为在求各元素权重比例时是根据u、v的来确定的。

 scrx=(i+0.5)*(scrH/dstH) - 0.5scry=(j+0.5)*(scrW/dstW) - 0.5x=math.floor(scrx)y=math.floor(scry)u=scrx-xv=scry-y

只要u、v确定了,其权重也就确定了。那么就可以组成filter_bic。

自定义tensorflow的tf.image.resize_bicubic方法相关推荐

  1. 【TensorFlow】理解tf.nn.conv2d方法 ( 附代码详解注释 )

    最近在研究学习TensorFlow,在做识别手写数字的demo时,遇到了tf.nn.conv2d这个方法,查阅了官网的API 发现讲得比较简略,还是没理解.google了一下,参考了网上一些朋友写得博 ...

  2. 【Tensorflow】tf.nn.depthwise_conv2d如何实现深度卷积?+深度可分离卷积详解

    目录 常规卷积操作 深度可分离卷积 = 逐通道卷积+逐点卷积 1.逐通道卷积 2.逐点卷积 参数对比 介绍 实验 代码清单 一些轻量级的网络,如mobilenet中,会有深度可分离卷积depthwis ...

  3. Dive into TensorFlow - 解析 TF 核心抽象 op 算子

    TF 计算图从逻辑层来讲,由 op 与 tensor 构成.op 是项点代表计算单元,tensor 是边代表 op 之间流动的数据内容,两者配合以数据流图的形式来表达计算图.那么 op 对应的物理层实 ...

  4. 【Tensorflow】tf.nn.atrous_conv2d如何实现空洞卷积?膨胀卷积

    介绍 关于空洞卷积的理论可以查看以下链接,这里我们不详细讲理论: 1.Long J, Shelhamer E, Darrell T, et al. Fully convolutional networ ...

  5. tensorflow op tf.global_variables_initializer

    一.安装 目前用了tensorflow.deeplearning4j两个深度学习框架, tensorflow 之前一直支持到python 3.5,目前以更新到3.6,故安装最新版体验使用. 慢慢长征路 ...

  6. Tensorflow中tf.ConfigProto()详解

    参考Tensorflow Machine Leanrning Cookbook tf.ConfigProto()主要的作用是配置tf.Session的运算方式,比如gpu运算或者cpu运算 具体代码如 ...

  7. TensorFlow指定CPU和GPU方法

    TensorFlow指定CPU和GPU方法 TensorFlow 支持 CPU 和 GPU.它也支持分布式计算.可以在一个或多个计算机系统的多个设备上使用 TensorFlow. TensorFlow ...

  8. tensorflow中keep_prob的修改方法

    tensorflow中keep_prob的修改方法 warning: WARNING:tensorflow:From D:\software\pycharm_location\venv\Dehaze- ...

  9. TensorFlow高效读取数据的方法

    概述 最新上传的mcnn中有完整的数据读写示例,可以参考. 关于Tensorflow读取数据,官网给出了三种方法: 供给数据(Feeding): 在TensorFlow程序运行的每一步, 让Pytho ...

  10. 【TensorFlow】tf.nn.conv2d是怎样实现卷积的?

    int height_col= (height + 2 * pad_h - kernel_h) / stride_h + 1; int width_col = (width + 2 * pad_w - ...

最新文章

  1. Playboy封面女郎、互联网第一夫人,程序员们的“钢铁审美”
  2. 多个相机拍摄定位_两种方式拍照易泄露隐私 | 如何避免照片记录 iPhone 定位信息?...
  3. 洛谷P3773 [CTSC2017]吉夫特(Lucas定理,dp)
  4. Hadoop 数据类型与文件结构剖析 Sequence, Map, Set, Array, BloomMap Files
  5. 仿写strncmp函数
  6. 11g RAC OCR,VOTING DISK存储全部损坏,利用自动备份,恢复OCR,VOTING DISK到新存储。...
  7. QT的QDomElement类的使用
  8. 【工业控制】OmniCal软件安装和使用详解
  9. node实现watcher的困境
  10. tcpdump抓两个网卡的包_tcpdump 抓包统计 分享助,请问用tcpdump抓包后,如何查看每个包...
  11. python写文字方法_Transcrypt: 用Python写js的方法
  12. IBM虚拟化全接触 实现从虚拟化到云端的数据中心
  13. (附源码)springboot高校科研管理系统 毕业设计 222055
  14. DB9标准的公头\母头接线定义
  15. 如何使用Apple Watch控制Apple TV
  16. 1047: 对数表 Java
  17. IDEA导入一个项目
  18. 02-Ceph 集群部署
  19. 电镀面积计算机公式,教你正确的计算电镀中施镀面积方法。
  20. App渠道安装来源识别与匹配

热门文章

  1. LAMMPS生成粗糙表面的in文件脚本(可调节微结构高、长和宽)
  2. 欢迎空心字怎么写_春节快乐空心字怎么写
  3. 分享几个快乐有趣的网站
  4. java枚举类规范写法
  5. 【Java合并图片】使用Java实现两张图片合并成一张图片的功能(水平合并、垂直合并、透明背景颜色)
  6. 中国最美的一千个汉字 : 千字文
  7. PHP——常用的魔术方法(保姆级教学)
  8. 使用Python对比两个excel表格中的重复数据
  9. 对话斯坦福商学院教授:颠覆大公司的不是技术,是商业模式
  10. 笔记本电脑频繁自动重启_电脑老是自动重启,电脑总是自动重启 - 电脑经常自动重启的解决办法 - 安全专题...