YUV转RGB汇总

#region ******************彩色图像记录的格式,YUV 转 RGB******************

        /* YUV, http://zh.wikipedia.org/wiki/YUV#.E5.B8.B8.E7.94.A8.E7.9A.84YUV.E6.A0.BC.E5.BC.8F
         *  作为视频媒体类型的辅助说明类型(Subtype),它们对应的GUID如下:
         *  在DirectShow中,常见的RGB和YUV格式
            GUID                                格式描述
            MEDIASUBTYPE_RGB1                       2色,每个像素用1位表示,需要调色板
            MEDIASUBTYPE_RGB4                       16色,每个像素用4位表示,需要调色板
            MEDIASUBTYPE_RGB8                       256色,每个像素用8位表示,需要调色板
            MEDIASUBTYPE_RGB565                     每个像素用16位表示,RGB分量分别使用5位、6位、5位
            MEDIASUBTYPE_RGB555                     每个像素用16位表示,RGB分量都使用5位(剩下的1位不用)
            MEDIASUBTYPE_RGB24                      每个像素用24位表示,RGB分量各使用8位
            MEDIASUBTYPE_RGB32                      每个像素用32位表示,RGB分量各使用8位(剩下的8位不用)
            MEDIASUBTYPE_ARGB32                     每个像素用32位表示,RGB分量各使用8位(剩下的8位用于表示Alpha通道值)
         *
            MEDIASUBTYPE_YUY2                    YUY2格式,以4:2:2方式打包
            MEDIASUBTYPE_YUYV                    YUYV格式(实际格式与YUY2相同)
            MEDIASUBTYPE_YVYU                    YVYU格式,以4:2:2方式打包
            MEDIASUBTYPE_UYVY                    UYVY格式,以4:2:2方式打包
            MEDIASUBTYPE_AYUV                    带Alpha通道的4:4:4 YUV格式
            MEDIASUBTYPE_Y41P                    Y41P格式,以4:1:1方式打包
            MEDIASUBTYPE_Y411                    Y411格式(实际格式与Y41P相同)
            MEDIASUBTYPE_Y211                    Y211格式
            MEDIASUBTYPE_IF09                    IF09格式
            MEDIASUBTYPE_IYUV                    IYUV格式
            MEDIASUBTYPE_YV12                    YV12格式
            MEDIASUBTYPE_YVU9                    YVU9格式
         */


        #region RGB TO RGB32

        /* RGB32 */
        /// <summary>
        /// 根据指针地址及内存块长度,获取RGB32数组
        /// @20140516
        /// </summary>
        /// <param name="pBuffer"></param>
        /// <param name="lBufferSize"></param>
        /// <param name="lPicWidth"></param>
        /// <param name="lPicHeight"></param>
        /// <returns></returns>
        public static byte[] GetRgb32_FromIntptr(IntPtr pBuffer, Int32 lBufferSize, Int32 lPicWidth, Int32 lPicHeight)
        {
            // 设置数组大小
            byte[] rgba32 = new byte[lPicHeight * lPicWidth * 4];
            Marshal.Copy(pBuffer, rgba32, 0, lBufferSize);

            return rgba32;
        }

        #endregion


        #region YUV TO RGB32

        /// <summary>
        /// YUV 转 RGB32
        /// </summary>
        /// <param name="pYPlaneByte"></param>
        /// <param name="pVPlaneByte"></param>
        /// <param name="pUPlaneByte"></param>
        /// <param name="lPicHeight"></param>
        /// <param name="lPicWidth"></param>
        /// <param name="bError">是否存在错误</param>
        /// <param name="strErrorDesc">错误消息</param>
        /// <returns></returns>
        public static byte[] GetRgb32_From_Yuv(byte[] pYPlaneByte, byte[] pUPlaneByte, byte[] pVPlaneByte, Int32 lPicHeight, Int32 lPicWidth, ref bool bError, ref string strErrorDesc)
        {
            bError = true;
            strErrorDesc = "无错误";
            int picSize = lPicWidth * lPicHeight;

            byte[] pRrgaByte = new byte[picSize * 4];
            int A = 0;

            try
            {
                for (int iRow = 0; iRow < lPicHeight; iRow++)
                {
                    for (int jCol = 0; jCol < lPicWidth; jCol++)
                    {
                        //int Y = pYPlane[i * dwWidth + j];
                        //int U = pUPlane[(i / 2) * (dwWidth / 2) + (j / 2)];
                        //int V = pVPlane[(i / 2) * (dwWidth / 2) + (j / 2)];
                        //int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
                        //int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
                        //int B = Y + (V - 128) + (((V - 128) * 198) >> 8);
                        //R = max(0, min(255, R));
                        //G = max(0, min(255, G));
                        //B = max(0, min(255, B));

                        int Y = pYPlaneByte[iRow * lPicWidth + jCol];
                        int U = pUPlaneByte[(iRow / 2) * (lPicWidth / 2) + (jCol / 2)];
                        int V = pVPlaneByte[(iRow / 2) * (lPicWidth / 2) + (jCol / 2)];

                        /* 存在颜色转换错误问题。现象:公交车编号的LED灯显示颜色,与实际不符。如K30,图片上的30颜色为蓝色,但实际上30是红色 */
                        /* 1、宣传横幅,红色变成蓝色 */
                        /* 2、公交编号LED,红色变成蓝色 */
                        /* 3、信号灯LED,红色变成蓝色 */
                        //int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
                        //int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
                        //int B = Y + (V - 128) + (((V - 128) * 198) >> 8);

                        // http://zh.wikipedia.org/wiki/YUV#YV12
                        //Int32 R = Y + 1.13983 * (V - 128);
                        //Int32 G = Y - 0.39456 * (U - 128) - 0.58060 * (V - 128);
                        //Int32 B = Y + 2.03211 * (U - 128);

                        /* 2012.12.24,纠正颜色错位问题R与B */
                        int R = Y + (V - 128) + (((V - 128) * 103) >> 8);
                        int G = Y - (((U - 128) * 88) >> 8) - (((V - 128) * 183) >> 8);
                        int B = Y + (U - 128) + (((U - 128) * 198) >> 8);

                        R = Math.Max(0, Math.Min(255, R));
                        G = Math.Max(0, Math.Min(255, G));
                        B = Math.Max(0, Math.Min(255, B));

                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(B);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(R);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);

                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(R);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(B);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);
                    }
                }

                return pRrgaByte;
            }
            catch (Exception exp)
            {
                pRrgaByte = null;

                bError = false;
                strErrorDesc = exp.ToString();
                //throw;
            }

            return null;
        }

        /* YUV420 / I420 
         * 存储格式:yuv yuv yuv
         */

        /* YUV420P: 
         * 存储格式:yyyyyyyy uuuuuuuu vvvvv
         * yuv420p 和 YUV420的区别 在存储格式上有区别
            yuv420p:yyyyyyyy uuuuuuuu vvvvv
            yuv420: yuv yuv yuv
         */

        /// <summary>
        /// GetRgb32_From_Yuv420
        /// @20140521,内存排列顺序有疑问
        /// </summary>
        /// <param name="pYuvPackedByte"></param>
        /// <param name="lBuffSize"></param>
        /// <param name="lPicHeight"></param>
        /// <param name="lPicWidth"></param>
        /// <param name="bError"></param>
        /// <param name="strErrorDesc"></param>
        /// <returns></returns>
        public static byte[] GetRgb32_From_Yuv420(byte[] pYuvPackedByte, Int32 lBuffSize, Int32 lPicHeight, Int32 lPicWidth, ref bool bError, ref string strErrorDesc)
        {
            bError = true;
            int picSize = lPicWidth * lPicHeight;
            strErrorDesc = string.Format("无错误.picSize4 = '{0}', lBuffSize1.5 = '{1}'", picSize * 4, lBuffSize * 3 / 2);

            byte[] pRrgaByte = new byte[picSize * 4];
            int A = 0;

            try
            {
                for (int iRow = 0; iRow < lPicHeight; iRow++)
                {
                    for (int jCol = 0; jCol < lPicWidth; jCol++)
                    {
                        int Y = pYuvPackedByte[iRow * lPicWidth + jCol + 0];
                        int U = pYuvPackedByte[iRow * lPicWidth + jCol + 1];
                        int V = pYuvPackedByte[iRow * lPicWidth + jCol + 2];

                        // http://zh.wikipedia.org/wiki/YUV#YV12
                        //Int32 R = Y + 1.13983 * (V - 128);
                        //Int32 G = Y - 0.39456 * (U - 128) - 0.58060 * (V - 128);
                        //Int32 B = Y + 2.03211 * (U - 128);

                        /* 2012.12.24,纠正颜色错位问题R与B */
                        int R = Y + (V - 128) + (((V - 128) * 103) >> 8);
                        int G = Y - (((U - 128) * 88) >> 8) - (((V - 128) * 183) >> 8);
                        int B = Y + (U - 128) + (((U - 128) * 198) >> 8);

                        R = Math.Max(0, Math.Min(255, R));
                        G = Math.Max(0, Math.Min(255, G));
                        B = Math.Max(0, Math.Min(255, B));

                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(B);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(R);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);

                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(R);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(B);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);
                    }
                }

                return pRrgaByte;
            }
            catch (Exception exp)
            {
                pRrgaByte = null;

                bError = false;
                strErrorDesc = exp.ToString();
                //throw;
            }

            return null;
        }

        /* UYVY / YVYU */

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pYuvPackedByte"></param>
        /// <param name="lBuffSize"></param>
        /// <param name="lPicHeight"></param>
        /// <param name="lPicWidth"></param>
        /// <param name="bError"></param>
        /// <param name="strErrorDesc"></param>
        /// <returns></returns>
        public static byte[] GetRgb32_From_Uyvy(byte[] pYuvPackedByte, Int32 lBuffSize, Int32 lPicHeight, Int32 lPicWidth, ref bool bError, ref string strErrorDesc)
        {
            bError = true;
            int picSize = lPicWidth * lPicHeight;
            strErrorDesc = string.Format("无错误.picSize4 = '{0}', lBuffSize2 = '{1}'", picSize * 4, lBuffSize * 2);

            byte[] pRrgaByte = new byte[picSize * 4];
            //int A = 0;

            byte[] pYPlaneByte;
            byte[] pUPlaneByte;
            byte[] pVPlaneByte;

            try
            {
                // 使用每行跨距
                pYPlaneByte = new byte[lPicHeight * lPicWidth];
                pUPlaneByte = new byte[(lPicHeight) * (lPicWidth / 2)];
                pVPlaneByte = new byte[(lPicHeight) * (lPicWidth / 2)];

                for (int idxSrc = 0, idxDest = 0; idxSrc < lBuffSize; )
                {
                    pUPlaneByte[idxDest * 2] = pYuvPackedByte[idxSrc + 0];
                    pYPlaneByte[idxDest] = pYuvPackedByte[idxSrc + 1];

                    pVPlaneByte[idxDest * 2 + 1] = pYuvPackedByte[idxSrc + 2];
                    pYPlaneByte[idxDest] = pYuvPackedByte[idxSrc + 3];

                    idxSrc = idxSrc + 4;
                    idxDest++;
                }

                pRrgaByte = GetRgb32_From_Yuv(pYPlaneByte, pUPlaneByte, pVPlaneByte, lPicHeight, lPicWidth, ref bError, ref strErrorDesc);

                pYPlaneByte = null;
                pUPlaneByte = null;
                pVPlaneByte = null;

                return pRrgaByte;
            }
            catch (Exception exp)
            {
                pYPlaneByte = null;
                pUPlaneByte = null;
                pVPlaneByte = null;

                pRrgaByte = null;

                bError = false;
                strErrorDesc = exp.ToString();
                //throw;
            }

            return null;
        }

        /* YV12 / I420 
         * 
         * YV12格式与IYUV类似,每个像素都提取Y,在UV提取时,将图像2 x 2的矩阵,每个矩阵提取一个U和一个V。
         * 
         * YV12格式和I420格式的不同处在V平面和U平面的位置不同。在YV12格式中,V平面紧跟在Y平面之后,然后才是U平面(即:YVU);但I420则是相反(即:YUV)。
         * 
         * NV12与YV12类似,效果一样,YV12中U和V是连续排列的,而在NV12中,U和V就交错排列的。
         */

        /// <summary>
        /// YV12 转 RGB32
        /// @在YV12格式中,V平面紧跟在Y平面之后,然后才是U平面(即:YVU);但I420则是相反(即:YUV)
        /// </summary>
        /// <param name="pYPlaneByte"></param>
        /// <param name="pVPlaneByte"></param>
        /// <param name="pUPlaneByte"></param>
        /// <param name="lPicHeight"></param>
        /// <param name="lPicWidth"></param>
        /// <returns></returns>
        public static byte[] GetRgb32_From_Yv12(byte[] pYPlaneByte, byte[] pUPlaneByte, byte[] pVPlaneByte, Int32 lPicHeight, Int32 lPicWidth)
        {
            int picSize = lPicWidth * lPicHeight;

            byte[] pRrgaByte = new byte[picSize * 4];
            int A = 0;

            try
            {
                for (int iRow = 0; iRow < lPicHeight; iRow++)
                {
                    for (int jCol = 0; jCol < lPicWidth; jCol++)
                    {
                        //int Y = pYPlane[i * dwWidth + j];
                        //int U = pUPlane[(i / 2) * (dwWidth / 2) + (j / 2)];
                        //int V = pVPlane[(i / 2) * (dwWidth / 2) + (j / 2)];
                        //int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
                        //int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
                        //int B = Y + (V - 128) + (((V - 128) * 198) >> 8);
                        //R = max(0, min(255, R));
                        //G = max(0, min(255, G));
                        //B = max(0, min(255, B));

                        int Y = pYPlaneByte[iRow * lPicWidth + jCol];
                        int U = pUPlaneByte[(iRow / 2) * (lPicWidth / 2) + (jCol / 2)];
                        int V = pVPlaneByte[(iRow / 2) * (lPicWidth / 2) + (jCol / 2)];

                        /* 存在颜色转换错误问题。现象:公交车编号的LED灯显示颜色,与实际不符。如K30,图片上的30颜色为蓝色,但实际上30是红色 */
                        /* 1、宣传横幅,红色变成蓝色 */
                        /* 2、公交编号LED,红色变成蓝色 */
                        /* 3、信号灯LED,红色变成蓝色 */
                        //int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
                        //int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
                        //int B = Y + (V - 128) + (((V - 128) * 198) >> 8);

                        /* 2012.12.24,纠正颜色错位问题R与B */
                        int R = Y + (V - 128) + (((V - 128) * 103) >> 8);
                        int G = Y - (((U - 128) * 88) >> 8) - (((V - 128) * 183) >> 8);
                        int B = Y + (U - 128) + (((U - 128) * 198) >> 8);

                        R = Math.Max(0, Math.Min(255, R));
                        G = Math.Max(0, Math.Min(255, G));
                        B = Math.Max(0, Math.Min(255, B));

                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(B);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(R);
                        pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);

                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(R);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(B);
                        //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);
                    }
                }

                return pRrgaByte;
            }
            catch (Exception exp)
            {
                pRrgaByte = null;
                //throw;
            }

            return null;
        }

        ///// <summary>
        ///// YV12 转 RGB32(GetRgb32_From_Yv12_20130417,纠正R与B的转换错误)
        ///// </summary>
        ///// <param name="pYPlaneByte"></param>
        ///// <param name="pUPlaneByte"></param>
        ///// <param name="pVPlaneByte"></param>
        ///// <param name="lPicHeight"></param>
        ///// <param name="lPicWidth"></param>
        ///// <returns></returns>
        //public static byte[] GetRgb32_From_Yv12_20130417(byte[] pYPlaneByte, byte[] pUPlaneByte, byte[] pVPlaneByte, Int32 lPicHeight, Int32 lPicWidth)
        //{
        //    try
        //    {
        //        int picSize = lPicWidth * lPicHeight;

        //        byte[] pRrgaByte = new byte[picSize * 4];
        //        int A = 0;

        //        for (int iRow = 0; iRow < lPicHeight; iRow++)
        //        {
        //            for (int jCol = 0; jCol < lPicWidth; jCol++)
        //            {
        //                //int Y = pYPlane[i * dwWidth + j];
        //                //int U = pUPlane[(i / 2) * (dwWidth / 2) + (j / 2)];
        //                //int V = pVPlane[(i / 2) * (dwWidth / 2) + (j / 2)];
        //                //int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
        //                //int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
        //                //int B = Y + (V - 128) + (((V - 128) * 198) >> 8);
        //                //R = max(0, min(255, R));
        //                //G = max(0, min(255, G));
        //                //B = max(0, min(255, B));

        //                int Y = pYPlaneByte[iRow * lPicWidth + jCol];
        //                int U = pUPlaneByte[(iRow / 2) * (lPicWidth / 2) + (jCol / 2)];
        //                int V = pVPlaneByte[(iRow / 2) * (lPicWidth / 2) + (jCol / 2)];

        //                /* 存在颜色转换错误问题。现象:公交车编号的LED灯显示颜色,与实际不符。如K30,图片上的30颜色为蓝色,但实际上30是红色 */
        //                //int R = Y + (U - 128) + (((U - 128) * 103) >> 8);
        //                //int G = Y - (((V - 128) * 88) >> 8) - (((U - 128) * 183) >> 8);
        //                //int B = Y + (V - 128) + (((V - 128) * 198) >> 8);

        //                /* 2012.12.24,纠正颜色错位问题R与B */
        //                int R = Y + (V - 128) + (((V - 128) * 103) >> 8);
        //                int G = Y - (((U - 128) * 88) >> 8) - (((V - 128) * 183) >> 8);
        //                int B = Y + (U - 128) + (((U - 128) * 198) >> 8);

        //                R = Math.Max(0, Math.Min(255, R));
        //                G = Math.Max(0, Math.Min(255, G));
        //                B = Math.Max(0, Math.Min(255, B));

        //                pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(B);
        //                pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
        //                pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(R);
        //                pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);

        //                //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 0] = Convert.ToByte(R);
        //                //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 1] = Convert.ToByte(G);
        //                //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 2] = Convert.ToByte(B);
        //                //pRrgaByte[4 * (iRow * lPicWidth + jCol) + 3] = Convert.ToByte(A);
        //            }
        //        }

        //        return pRrgaByte;
        //    }
        //    catch (Exception exp)
        //    {
        //        //throw;
        //    }

        //    return null;
        //}


        static double[,] YUV2RGB_CONVERT_MATRIX = new double[3, 3] { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };

        /// <summary>
        /// YUV420 转 RGB 参考地址http://www.haogongju.net/art/953565
        /// </summary>
        /// <param name="yuvFrame">yuv数据</param>
        /// <param name="width">图片宽</param>
        /// <param name="height">图片高</param>
        /// <returns></returns>
        public static byte[] ConvertYUV420RGB(byte[] yuvFrame, int width, int height)
        {

            int gIndex = width * height;
            int uIndex = width * height;
            int vIndex = uIndex + ((width * height) >> 2);

            byte[] rgbFrame = new byte[gIndex * 3];

            int bIndex = gIndex * 2;

            int temp = 0;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {

                    // R分量       
                    temp = (int)(yuvFrame[y * width + x] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[0, 2]);
                    rgbFrame[y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));

                    // G分量      
                    temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1, 1] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1, 2]);
                    rgbFrame[gIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));

                    // B分量            
                    temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[2, 1]);
                    rgbFrame[bIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
                    //rgbFrame[bIndex + y * width + x] = Math.Max(0, Math.Min(255, temp));

                }
            }
            return rgbFrame;

        }

        public static byte[] ConvertYUV420RGB(byte[] pYPlaneByte, byte[] pUPlaneByte, byte[] pVPlaneByte, Int32 lPicHeight, Int32 lPicWidth)
        {

            return null;
        }

        #endregion

        #endregion

发布了20 篇原创文章 · 获赞 5 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/mt122/article/details/38459963
今日推荐