亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

決勝圣誕,女神心情不用猜!

nanfeiyan / 1230人閱讀

摘要:萬(wàn)萬(wàn)沒(méi)想到,在圣誕節(jié)前夕,女神居然答應(yīng)了在下的約會(huì)請(qǐng)求。想在下正如在座的一些看官一樣,雖玉樹(shù)臨風(fēng)風(fēng)流倜儻,卻總因猜不透女孩的心思,一不留神就落得個(gè)母胎單身。在內(nèi)部將張量表示為基本數(shù)據(jù)類型的維數(shù)組。

本文將結(jié)合移動(dòng)設(shè)備攝像能力與 TensorFlow.js,在瀏覽器里實(shí)現(xiàn)一個(gè)實(shí)時(shí)的人臉情緒分類器。鑒于文章的故事背景較長(zhǎng),對(duì)實(shí)現(xiàn)本身更有興趣的同學(xué)可直接跳轉(zhuǎn)至技術(shù)方案概述

DEMO 試玩

前言

看遍了 25 載的雪月沒(méi)風(fēng)花,本旺早已不悲不喜。萬(wàn)萬(wàn)沒(méi)想到,在圣誕節(jié)前夕,女神居然答應(yīng)了在下的約會(huì)請(qǐng)求。可是面對(duì)這么個(gè)大好機(jī)會(huì),本前端工程獅竟突然慌張起來(lái)。想在下正如在座的一些看官一樣,雖玉樹(shù)臨風(fēng)、風(fēng)流倜儻,卻總因猜不透女孩的心思,一不留神就落得個(gè)母胎單身。如今已是 8102 年,像我等這么優(yōu)秀的少年若再不脫單,黨和人民那都是一萬(wàn)個(gè)不同意!痛定思痛,在下這就要發(fā)揮自己的技術(shù)優(yōu)勢(shì),將察「顏」觀色的技能樹(shù)點(diǎn)滿,做一個(gè)洞悉女神喜怒哀愁的優(yōu)秀少年,決勝圣誕之巔!

正題開(kāi)始 需求分析

我們前端工程師終于在 2018 年迎來(lái)了 TensorFlow.js,這就意味著,就算算法學(xué)的再弱雞,又不會(huì) py 交易,我們也能靠著 js 跟著算法的同學(xué)們學(xué)上個(gè)一招半式。如果我們能夠在約會(huì)期間,通過(guò)正規(guī)渠道獲得女神的照片,是不是就能用算法分析分析女神看到在下的時(shí)候,是開(kāi)心還是...不,一定是開(kāi)心的!

可是,約會(huì)的戰(zhàn)場(chǎng)瞬息萬(wàn)變,我們總不能拍了照就放手機(jī)里,約完會(huì)回到靜悄悄的家,再跑代碼分析吧,那可就 「too young too late」 了!時(shí)間就是生命,如果不能當(dāng)場(chǎng)知道女神的心情,我們還不如給自己 -1s!

因此,我們的目標(biāo)就是能夠在手機(jī)上,實(shí)時(shí)看到這樣的效果(嘛,有些簡(jiǎn)陋,不過(guò)本文將專注于功能實(shí)現(xiàn),哈哈):

技術(shù)方案概述

很簡(jiǎn)單,我們需要的就兩點(diǎn),圖像采集 & 模型應(yīng)用,至于結(jié)果怎么展示,嗨呀,作為一個(gè)前端工程師,render 就是家常便飯呀。對(duì)于前端的同學(xué)來(lái)說(shuō),唯一可能不熟悉的也就是算法模型怎么用;對(duì)于算法的同學(xué)來(lái)說(shuō),唯一可能不熟悉的也就是移動(dòng)設(shè)備怎么使用攝像頭。

我們的流程即如下圖所示(下文會(huì)針對(duì)計(jì)算速度的問(wèn)題進(jìn)行優(yōu)化):

下面,我們就根據(jù)這個(gè)流程圖來(lái)梳理下如何實(shí)現(xiàn)吧!

核心一:圖像采集與展示 圖像采集

我們?nèi)绾问褂?strong>移動(dòng)設(shè)備進(jìn)行圖像或者視頻流的采集呢?這就需要借助 WebRTC 了。WebRTC,即網(wǎng)頁(yè)即時(shí)通信(Web Real-Time Communication),是一個(gè)支持網(wǎng)頁(yè)瀏覽器進(jìn)行實(shí)時(shí)語(yǔ)音對(duì)話或視頻對(duì)話的 API。它于 2011 年 6 月 1 日開(kāi)源,并在 Google、Mozilla、Opera 支持下被納入萬(wàn)維網(wǎng)聯(lián)盟的 W3C 推薦標(biāo)準(zhǔn)。

拉起攝像頭并獲取采集到的視頻流,這正是我們需要使用到的由 WebRTC 提供的能力,而核心的 API 就是 navigator.mediaDevices.getUserMedia

該方法的兼容性如下,可以看到,對(duì)于常見(jiàn)的手機(jī)來(lái)說(shuō),還是可以較好支持的。不過(guò),不同手機(jī)、系統(tǒng)種類與版本、瀏覽器種類與版本可能還是存在一些差異。如果想要更好的做兼容的話,可以考慮使用 Adapter.js 來(lái)做 shim,它可以讓我們的 App 與 Api 的差異相隔離。此外,在這里可以看到一些有趣的例子。具體 Adapter.js 的實(shí)現(xiàn)可以自行查閱。

那么這個(gè)方法是如何使用的呢?我們可以通過(guò) MDN 來(lái)查閱一下。MediaDevices getUserMedia() 會(huì)向用戶申請(qǐng)權(quán)限,使用媒體輸入,獲得具有指定類型的 MediaStream(如音頻流、視頻流),并且會(huì) resolve 一個(gè) MediaStream 對(duì)象,如果沒(méi)有權(quán)限或沒(méi)有匹配的媒體,會(huì)報(bào)出相應(yīng)異常:

navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
  /* use the stream */
})
.catch(function(err) {
  /* handle the error */
});

因此,我們可以在入口文件統(tǒng)一這樣做:

class App extends Component {
  constructor(props) {
    super(props);
    // ...
    this.stream = null;
    this.video = null;
    // ...
  }

  componentDidMount() {
    // ...
    this.startMedia();
  }

  startMedia = () => {
    const constraints = {
      audio: false,
      video: true,
    };
    navigator.mediaDevices.getUserMedia(constraints)
      .then(this.handleSuccess)
      .catch(this.handleError);
  }

  handleSuccess = (stream) => {
    this.stream = stream; // 獲取視頻流
    this.video.srcObject = stream; // 傳給 video
  }
  
  handleError = (error) => {
    console.log("navigator.getUserMedia error: ", error);
  }
  // ...
}
實(shí)時(shí)展示

為什么需要 this.video 呢,我們不僅要展示拍攝到的視頻流,還要能直觀的將女神的面部神情標(biāo)記出來(lái),因此需要通過(guò) canvas 來(lái)同時(shí)實(shí)現(xiàn)展示視頻流和繪制基本圖形這兩點(diǎn),而連接這兩點(diǎn)的方法如下:

canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);

當(dāng)然,我們并不需要在視圖中真的提供一個(gè) video DOM,而是由 App 維護(hù)在實(shí)例內(nèi)部即可。canvas.width 和 canvas.height 需要考慮移動(dòng)端設(shè)備的尺寸,這里略去不表。

而繪制矩形框與文字信息則非常簡(jiǎn)單,我們只需要拿到算法模型計(jì)算出的位置信息即可:

export const drawBox = ({ ctx, x, y, w, h, emoji }) => {
  ctx.strokeStyle = EmojiToColor[emoji];
  ctx.lineWidth = "4";
  ctx.strokeRect(x, y, w, h);
}

export const drawText = ({ ctx, x, y, text }) => {
  const padding = 4
  ctx.fillStyle = "#ff6347"
  ctx.font = "16px"
  ctx.textBaseline = "top"
  ctx.fillText(text, x + padding, y + padding)
}
核心二:模型預(yù)測(cè)

在這里,我們需要將問(wèn)題進(jìn)行拆解。鑒于本文所說(shuō)的「識(shí)別女神表情背后的情緒」屬于圖像分類問(wèn)題,那么這個(gè)問(wèn)題就需要我們完成兩件事:

從圖像中提取出人臉部分的圖像;

將提取出的圖像塊作為輸入交給模型進(jìn)行分類計(jì)算。

下面我們來(lái)圍繞這兩點(diǎn)逐步討論。

人臉提取

我們將借助 face-api.js 來(lái)處理。face-api.js 是基于 tensorflow.js 核心 API (@tensorflow/tfjs-core) 來(lái)實(shí)現(xiàn)的在瀏覽器環(huán)境中使用的面部檢測(cè)與識(shí)別庫(kù),本身就提供了
SSD Mobilenet V1、Tiny Yolo V2、MTCNN 這三種非常輕量的、適合移動(dòng)設(shè)備使用的模型。很好理解的是效果自然是打了不少折扣,這些模型都從模型大小、計(jì)算復(fù)雜度、機(jī)器功耗等多方面做了精簡(jiǎn),盡管有些專門用來(lái)計(jì)算的移動(dòng)設(shè)備還是可以駕馭完整模型的,但我們一般的手機(jī)是肯定沒(méi)有卡的,自然只能使用 Mobile 版的模型。

這里我們將使用 MTCNN。我們可以小瞄一眼模型的設(shè)計(jì),如下圖所示??梢钥吹剑覀兊膱D像幀會(huì)被轉(zhuǎn)換成不同 size 的張量傳入不同的 net,并做了一堆 Max-pooling,最后同時(shí)完成人臉?lè)诸悺b box 的回歸與 landmark 的定位。大致就是說(shuō),輸入一張圖像,我們可以得到圖像中所有人臉的類別、檢測(cè)框的位置信息和比如眼睛、鼻子、嘴唇的更細(xì)節(jié)的位置信息。

當(dāng)然,當(dāng)我們使用了 face-api.js 時(shí)就不需要太仔細(xì)的去考慮這些,它做了較多的抽象與封裝,甚至非常兇殘的對(duì)前端同學(xué)屏蔽了張量的概念,你只需要取到一個(gè) img DOM,是的,一個(gè)已經(jīng)加載好 src 的 img DOM 作為封裝方法的輸入(加載 img 就是一個(gè) Promise 咯),其內(nèi)部會(huì)自己轉(zhuǎn)換成需要的張量。通過(guò)下面的代碼,我們可以將視頻幀中的人臉提取出來(lái)。

export class FaceExtractor {
  constructor(path = MODEL_PATH, params = PARAMS) {
    this.path = path;
    this.params = params;
  }

  async load() {
    this.model = new faceapi.Mtcnn();
    await this.model.load(this.path);
  }

  async findAndExtractFaces(img) {
    // ...一些基本判空保證在加載好后使用
    const input = await faceapi.toNetInput(img, false, true);
    const results = await this.model.forward(input, this.params);
    const detections = results.map(r => r.faceDetection);
    const faces = await faceapi.extractFaces(input.inputs[0], detections);
    return { detections, faces };
  }
}
情緒分類

好了,終于到了核心功能了!一個(gè)「好」習(xí)慣是,扒一扒 GitHub 看看有沒(méi)有開(kāi)源代碼可以參考一下,如果你是大佬請(qǐng)當(dāng)我沒(méi)說(shuō)。這里我們將使用一個(gè)實(shí)時(shí)面部檢測(cè)和情緒分類模型來(lái)完成我們的核心功能,這個(gè)模型可以區(qū)分開(kāi)心、生氣、難過(guò)、惡心、沒(méi)表情等。

對(duì)于在瀏覽器中使用 TensorFlow.js 而言,很多時(shí)候我們更多的是應(yīng)用現(xiàn)有模型,通過(guò) tfjs-converter 來(lái)將已有的 TensorFlow 的模型、Keras 的模型轉(zhuǎn)換成 tfjs 可以使用的模型。值得一提的是,手機(jī)本身集成了很多的傳感器,可以采集到很多的數(shù)據(jù),相信未來(lái)一定有 tfjs 發(fā)揮的空間。具體轉(zhuǎn)換方法可參考文檔,我們繼續(xù)往下講。

那么我們可以像使用 face-api.js 一樣將 img DOM 傳入模型嗎?不行,事實(shí)上,我們使用的模型的輸入并不是隨意的圖像,而是需要轉(zhuǎn)換到指定大小、并只保留灰度圖的張量。因此在繼續(xù)之前,我們需要對(duì)原圖像進(jìn)行一些預(yù)處理。

哈哈,躲得了初一躲不過(guò)十五,我們還是來(lái)了解下什么是張量吧!TensorFlow 的官網(wǎng)是這么解釋的:

張量是對(duì)矢量和矩陣向潛在的更高維度的泛化。TensorFlow 在內(nèi)部將張量表示為基本數(shù)據(jù)類型的 n 維數(shù)組。

算了沒(méi)關(guān)系,我們畫(huà)個(gè)圖來(lái)理解張量是什么樣的:

因此,我們可將其簡(jiǎn)單理解為更高維的矩陣,并且存儲(chǔ)的時(shí)候就是個(gè)數(shù)組套數(shù)組。當(dāng)然,我們通常使用的 RGB 圖像有三個(gè)通道,那是不是就是說(shuō)我們的圖像數(shù)據(jù)就是三維張量(寬、高、通道)了呢?也不是,在 TensorFlow 里,第一維通常是 n,具體來(lái)說(shuō)就是圖像個(gè)數(shù)(更準(zhǔn)確的說(shuō)法是 batch),因此一個(gè)圖像張量的 shape 一般是 [n, height, width, channel],也即四維張量。

那么我們要怎么對(duì)圖像進(jìn)行預(yù)處理呢?首先我們將分布在 [0, 255] 的像素值中心化到 [-127.5, 127.5],然后標(biāo)準(zhǔn)化到 [-1, 1]即可。

const NORM_OFFSET = tf.scalar(127.5);

export const normImg = (img, size) => {
  // 轉(zhuǎn)換成張量
  const imgTensor = tf.fromPixels(img);

  // 從 [0, 255] 標(biāo)準(zhǔn)化到 [-1, 1].
  const normalized = imgTensor
    .toFloat()
    .sub(NORM_OFFSET) // 中心化
    .div(NORM_OFFSET); // 標(biāo)準(zhǔn)化

  const { shape } = imgTensor;
  if (shape[0] === size && shape[1] === size) {
    return normalized;
  }

  // 按照指定大小調(diào)整
  const alignCorners = true;
  return tf.image.resizeBilinear(normalized, [size, size], alignCorners);
}

然后將圖像轉(zhuǎn)成灰度圖:

export const rgbToGray = async imgTensor => {
  const minTensor = imgTensor.min()
  const maxTensor = imgTensor.max()
  const min = (await minTensor.data())[0]
  const max = (await maxTensor.data())[0]
  minTensor.dispose()
  maxTensor.dispose()

  // 灰度圖則需要標(biāo)準(zhǔn)化到 [0, 1],按照像素值的區(qū)間來(lái)標(biāo)準(zhǔn)化
  const normalized = imgTensor.sub(tf.scalar(min)).div(tf.scalar(max - min))

  // 灰度值取 RGB 的平均值
  let grayscale = normalized.mean(2)

  // 擴(kuò)展通道維度來(lái)獲取正確的張量形狀 (h, w, 1)
  return grayscale.expandDims(2)
}

這樣一來(lái),我們的輸入就從 3 通道的彩色圖片變成了只有 1 個(gè)通道的黑白圖。

注意,我們這里所做的預(yù)處理比較簡(jiǎn)單,一方面我們?cè)诒苊馊ダ斫庖恍┘?xì)節(jié)問(wèn)題,另一方面也是因?yàn)槲覀兪窃谑褂靡呀?jīng)訓(xùn)練好的模型,不需要做一些復(fù)雜的預(yù)處理來(lái)改善訓(xùn)練的效果。

準(zhǔn)備好圖像后,我們需要開(kāi)始準(zhǔn)備模型了!我們的模型主要需要暴露加載模型的方法 load 和對(duì)圖像進(jìn)行分類的 classify 這兩個(gè)方法。加載模型非常簡(jiǎn)單,只需要調(diào)用 tf.loadModel 即可,需要注意的是,加載模型是一個(gè)異步過(guò)程。我們使用 create-react-app 構(gòu)建的項(xiàng)目,封裝的 Webpack 配置已經(jīng)支持了 async-await 的方法。

class Model {
  constructor({ path, imageSize, classes, isGrayscale = false }) {
    this.path = path
    this.imageSize = imageSize
    this.classes = classes
    this.isGrayscale = isGrayscale
  }

  async load() {
    this.model = await tf.loadModel(this.path)

    // 預(yù)熱一下
    const inShape = this.model.inputs[0].shape.slice(1)
    const result = tf.tidy(() => this.model.predict(tf.zeros([1, ...inShape])))
    await result.data()
    result.dispose()
  }

  async imgToInputs(img) {
    // 轉(zhuǎn)換成張量并 resize
    let norm = await prepImg(img, this.imageSize)
    // 轉(zhuǎn)換成灰度圖輸入
    norm = await rgbToGrayscale(norm)
    // 這就是所說(shuō)的設(shè)置 batch 為 1
    return norm.reshape([1, ...norm.shape])
  }

  async classify(img, topK = 10) {
    const inputs = await this.imgToInputs(img)
    const logits = this.model.predict(inputs)
    const classes = await this.getTopKClasses(logits, topK)
    return classes
  }

  async getTopKClasses(logits, topK = 10) {
    const values = await logits.data()
    let predictionList = []

    for (let i = 0; i < values.length; i++) {
      predictionList.push({ value: values[i], index: i })
    }

    predictionList = predictionList
      .sort((a, b) => b.value - a.value)
      .slice(0, topK)

    return predictionList.map(x => {
      return { label: this.classes[x.index], value: x.value }
    })
  }
}

export default Model

我們可以看到,我們的模型返回的是一個(gè)叫 logits 的量,而為了知道分類的結(jié)果,我們又做了 getTopKClasses 的操作。這可能會(huì)使得較少了解這塊的同學(xué)有些困惑。實(shí)際上,對(duì)于一個(gè)分類模型而言,我們返回的結(jié)果并不是一個(gè)特定的類,而是對(duì)各個(gè) class 的概率分布,舉個(gè)例子:

// 示意用
const classifyResult = [0.1, 0.2, 0.25, 0.15, 0.3];

也就是說(shuō),我們分類的結(jié)果其實(shí)并不是說(shuō)圖像中的東西「一定是人或者狗」,而是「可能是人或者可能是狗」。以上面的示意代碼為例,如果我們的 label 對(duì)應(yīng)的是 ["女人", "男人", "大狗子", "小狗子", "二哈"],那么上述的結(jié)果其實(shí)應(yīng)該理解為:圖像中的物體 25% 的可能性為大狗子,20% 的可能性為一個(gè)男人。

因此,我們需要做 getTopKClasses,根據(jù)我們的場(chǎng)景我們只關(guān)心最可能的情緒,那么我們也就會(huì)取 top1 的概率分布值,從而知道最可能的預(yù)測(cè)結(jié)果。

怎么樣,tfjs 封裝后的高級(jí)方法是不是在語(yǔ)義上較為清晰呢?

最終我們將上文提到的人臉提取功能與情緒分類模型整合到一起,并加上一些基本的 canvas 繪制:

  // 略有調(diào)整
  analyzeFaces = async (img) => {
    // ...
    const faceResults = await this.faceExtractor.findAndExtractFaces(img);
    const { detections, faces } = faceResults;

    // 對(duì)提取到的每一個(gè)人臉進(jìn)行分類
    let emotions = await Promise.all(
      faces.map(async face => await this.emotionModel.classify(face))
    );
    // ...
  }

  drawDetections = () => {
    const { detections, emotions } = this.state;
    if (!detections.length) return;

    const { width, height } = this.canvas;
    const ctx = this.canvas.getContext("2d");
    const detectionsResized = detections.map(d => d.forSize(width, height));
    detectionsResized.forEach((det, i) => {
      const { x, y } = det.box
      const { emoji, name } = emotions[i][0].label;
      drawBox({ ctx, ...det.box, emoji });
      drawText({ ctx, x, y, text: emoji, name });
    });
  }

大功告成!

實(shí)時(shí)性優(yōu)化

事實(shí)上,我們還應(yīng)該考慮的一個(gè)問(wèn)題是實(shí)時(shí)性。事實(shí)上,我們的計(jì)算過(guò)程用到了兩個(gè)模型,即便已經(jīng)是針對(duì)移動(dòng)設(shè)備做了優(yōu)化的精簡(jiǎn)模型,但仍然會(huì)存在性能問(wèn)題。如果我們?cè)诮M織代碼的時(shí)候以阻塞的方式進(jìn)行預(yù)測(cè),那么就會(huì)出現(xiàn)一幀一幀的卡頓,女神的微笑也會(huì)變得抖動(dòng)和僵硬。

因此,我們要考慮做一些優(yōu)化,來(lái)更好地畫(huà)出效果。

筆者這里利用一個(gè) flag 來(lái)標(biāo)記當(dāng)前是否有正在進(jìn)行的模型計(jì)算,如果有,則進(jìn)入下一個(gè)事件循環(huán),否則則進(jìn)入模型計(jì)算的異步操作。同時(shí),每一個(gè)事件循環(huán)都會(huì)執(zhí)行 canvas 操作,從而保證標(biāo)記框總是會(huì)展示出來(lái),且每次展示的其實(shí)都是緩存在 state 中的前一次模型計(jì)算結(jié)果。這種操作是具有合理性的,因?yàn)槿四樀囊苿?dòng)通常是連續(xù)的(如果不連續(xù)這個(gè)世界可能要重新審視一下),這種處理方法能較好的對(duì)結(jié)果進(jìn)行展示,且不會(huì)因?yàn)槟P陀?jì)算而阻塞,導(dǎo)致卡頓,本質(zhì)上是一種離散化采樣的技巧吧。

  handleSnapshot = async () => {
    // ... 一些 canvas 準(zhǔn)備操作
    canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
    this.drawDetections(); // 繪制 state 中維護(hù)的結(jié)果
    
    // 利用 flag 判斷是否有正在進(jìn)行的模型預(yù)測(cè)
    if (!this.isForwarding) {
      this.isForwarding = true;
      const imgSrc = await getImg(canvas.toDataURL("image/png"));
      this.analyzeFaces(imgSrc);
    }

    const that = this;
    setTimeout(() => {
      that.handleSnapshot();
    }, 10);
  }

  analyzeFaces = async (img) => {
    // ...其他操作
    const faceResults = await this.models.face.findAndExtractFaces(img);
    const { detections, faces } = faceResults;

    let emotions = await Promise.all(
      faces.map(async face => await this.models.emotion.classify(face))
    );
    this.setState(
      { loading: false, detections, faces, emotions },
      () => {
        // 獲取到新的預(yù)測(cè)值后,將 flag 置為 false,以再次進(jìn)行預(yù)測(cè)
        this.isForwarding = false;
      }
    );
  }
效果展示

我們來(lái)在女神這試驗(yàn)下效果看看:

嗯,馬馬虎虎吧!雖然有時(shí)候還是會(huì)把笑容識(shí)別成沒(méi)什么表情,咦,是不是 Gakki 演技還是有點(diǎn)…好了好了,時(shí)間緊迫,趕緊帶上武器準(zhǔn)備赴約吧。穿上一身帥氣格子衫,打扮成程序員模樣~

結(jié)尾

約會(huì)當(dāng)晚,吃著火鍋唱著歌,在下與女神相談甚歡。正當(dāng)氣氛逐漸曖昧,話題開(kāi)始深入到感情方面時(shí),我自然的問(wèn)起女神的理想型。萬(wàn)萬(wàn)沒(méi)想到,女神突然說(shuō)了這樣的話:

那一刻我想起了 Eason 的歌:

Lonely Lonely christmas  
Merry Merry christmas
寫了卡片能寄給誰(shuí)
心碎的像街上的紙屑
參考

https://developer.mozilla.org...

https://github.com/webrtc/ada...

https://github.com/justadudew...

https://github.com/tensorflow...

[Zhang K, Zhang Z, Li Z, et al. Joint face detection and alignment using multitask cascaded convolutional networks[J]. IEEE Signal Processing Letters, 2016, 23(10): 1499-1503.][7]

文章可隨意轉(zhuǎn)載,但請(qǐng)保留此 原文鏈接。

非常歡迎有激情的你加入 ES2049 Studio,簡(jiǎn)歷請(qǐng)發(fā)送至 caijun.hcj(at)alibaba-inc.com 。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/100361.html

相關(guān)文章

  • PNG圖片壓縮原理解析--屌絲的眼淚

    摘要:差分編碼的目的,就是盡可能的將圖片數(shù)據(jù)值轉(zhuǎn)換成一組重復(fù)的低的值,這樣的值更容易被壓縮。最后還要注意的是,差分編碼處理的是每一個(gè)的像素點(diǎn)中每條顏色通道的值,紅綠藍(lán)透明四個(gè)顏色通道的值分別進(jìn)行處理。 背景 今天凌晨一點(diǎn),突然有個(gè)人加我的qq,一看竟然是十年前被我刪掉的初戀。。。。 因?yàn)橹霸趒q空間有太多的互動(dòng),所以qq推薦好友里面經(jīng)常推薦我倆互相認(rèn)識(shí)。。。。謎之尷尬 showImg(ht...

    EsgynChina 評(píng)論0 收藏0
  • PNG圖片壓縮原理解析--屌絲的眼淚

    摘要:差分編碼的目的,就是盡可能的將圖片數(shù)據(jù)值轉(zhuǎn)換成一組重復(fù)的低的值,這樣的值更容易被壓縮。最后還要注意的是,差分編碼處理的是每一個(gè)的像素點(diǎn)中每條顏色通道的值,紅綠藍(lán)透明四個(gè)顏色通道的值分別進(jìn)行處理。 背景 今天凌晨一點(diǎn),突然有個(gè)人加我的qq,一看竟然是十年前被我刪掉的初戀。。。。 因?yàn)橹霸趒q空間有太多的互動(dòng),所以qq推薦好友里面經(jīng)常推薦我倆互相認(rèn)識(shí)。。。。謎之尷尬 showImg(ht...

    DevTalking 評(píng)論0 收藏0
  • PNG圖片壓縮原理解析--屌絲的眼淚

    摘要:差分編碼的目的,就是盡可能的將圖片數(shù)據(jù)值轉(zhuǎn)換成一組重復(fù)的低的值,這樣的值更容易被壓縮。最后還要注意的是,差分編碼處理的是每一個(gè)的像素點(diǎn)中每條顏色通道的值,紅綠藍(lán)透明四個(gè)顏色通道的值分別進(jìn)行處理。 背景 今天凌晨一點(diǎn),突然有個(gè)人加我的qq,一看竟然是十年前被我刪掉的初戀。。。。 因?yàn)橹霸趒q空間有太多的互動(dòng),所以qq推薦好友里面經(jīng)常推薦我倆互相認(rèn)識(shí)。。。。謎之尷尬 showImg(ht...

    Nino 評(píng)論0 收藏0
  • javascript設(shè)計(jì)模式(Alloy Team)

    摘要:小明追女神的故事小明遇到了他的女神,打算送一朵鮮花來(lái)表白。剛好小明打聽(tīng)到他和有一個(gè)共同的好友,于是小明決定讓來(lái)替自己來(lái)完成這件事。 1.單例模式 單例模式的核心:(1)確保只有一個(gè)實(shí)例(2)提供全局訪問(wèn) 用代理實(shí)現(xiàn)單例模式: var ProxySingletonCreateDiv = (function(){ var instance; return f...

    sf190404 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<