การใช้ Video Intelligence API กับ Python

1. ภาพรวม

cfaa6ffa7bc5ca70.png

Video Intelligence API ช่วยให้คุณใช้เทคโนโลยีการวิเคราะห์วิดีโอของ Google เป็นส่วนหนึ่งของแอปพลิเคชันได้

ในแล็บนี้ คุณจะมุ่งเน้นการใช้ Video Intelligence API กับ Python

สิ่งที่คุณจะได้เรียนรู้

  • วิธีตั้งค่าสภาพแวดล้อม
  • วิธีตั้งค่า Python
  • วิธีตรวจหาการเปลี่ยนช็อต
  • วิธีตรวจหาป้ายกำกับ
  • วิธีตรวจหาเนื้อหาที่อาจไม่เหมาะสม
  • วิธีถอดเสียงคำพูด
  • วิธีตรวจหาและติดตามข้อความ
  • วิธีตรวจจับและติดตามวัตถุ
  • วิธีตรวจจับและติดตามโลโก้

สิ่งที่คุณต้องมี

  • โปรเจ็กต์ Google Cloud
  • เบราว์เซอร์ เช่น Chrome หรือ Firefox
  • คุ้นเคยกับการใช้ Python

แบบสำรวจ

คุณจะใช้บทแนะนำนี้อย่างไร

อ่านอย่างเดียว อ่านและทำแบบฝึกหัด

คุณจะให้คะแนนประสบการณ์การใช้งาน Python เท่าใด

ผู้ฝึกหัด ขั้นกลาง ผู้ชำนาญ

คุณจะให้คะแนนประสบการณ์การใช้งานบริการของ Google Cloud เท่าไร

ผู้ฝึกหัด ขั้นกลาง ผู้ชำนาญ

2. การตั้งค่าและข้อกำหนด

การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง

  1. ลงชื่อเข้าใช้ Google Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • ชื่อโปรเจ็กต์คือชื่อที่แสดงสำหรับผู้เข้าร่วมโปรเจ็กต์นี้ ซึ่งเป็นสตริงอักขระที่ Google APIs ไม่ได้ใช้ คุณอัปเดตได้ทุกเมื่อ
  • รหัสโปรเจ็กต์จะไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมดและเปลี่ยนแปลงไม่ได้ (เปลี่ยนไม่ได้หลังจากตั้งค่าแล้ว) Cloud Console จะสร้างสตริงที่ไม่ซ้ำกันโดยอัตโนมัติ ซึ่งโดยปกติแล้วคุณไม่จำเป็นต้องสนใจว่าสตริงนั้นคืออะไร ใน Codelab ส่วนใหญ่ คุณจะต้องอ้างอิงรหัสโปรเจ็กต์ (โดยทั่วไปจะระบุเป็น PROJECT_ID) หากไม่ชอบรหัสที่สร้างขึ้น คุณอาจสร้างรหัสแบบสุ่มอีกรหัสหนึ่งได้ หรือคุณอาจลองใช้ชื่อของคุณเองและดูว่ามีชื่อนั้นหรือไม่ คุณจะเปลี่ยนแปลงรหัสนี้หลังจากขั้นตอนนี้ไม่ได้ และรหัสจะคงอยู่ตลอดระยะเวลาของโปรเจ็กต์
  • โปรดทราบว่ายังมีค่าที่ 3 ซึ่งคือหมายเลขโปรเจ็กต์ที่ API บางตัวใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าทั้ง 3 นี้ได้ในเอกสารประกอบ
  1. จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร/API ของ Cloud การทำตาม Codelab นี้จะไม่มีค่าใช้จ่ายมากนัก หรืออาจไม่มีค่าใช้จ่ายเลย หากต้องการปิดทรัพยากรเพื่อหลีกเลี่ยงการเรียกเก็บเงินนอกเหนือจากบทแนะนำนี้ คุณสามารถลบทรัพยากรที่สร้างขึ้นหรือลบโปรเจ็กต์ได้ ผู้ใช้ Google Cloud รายใหม่มีสิทธิ์เข้าร่วมโปรแกรมช่วงทดลองใช้ฟรีมูลค่า$300 USD

เริ่มต้น Cloud Shell

แม้ว่าคุณจะใช้งาน Google Cloud จากแล็ปท็อประยะไกลได้ แต่ใน Codelab นี้คุณจะใช้ Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานในระบบคลาวด์

เปิดใช้งาน Cloud Shell

  1. จาก Cloud Console ให้คลิกเปิดใช้งาน Cloud Shell 853e55310c205094.png

55efc1aaa7a4d3ad.png

หากคุณเริ่มใช้ Cloud Shell เป็นครั้งแรก คุณจะเห็นหน้าจอระดับกลางที่อธิบายว่า Cloud Shell คืออะไร หากเห็นหน้าจอระดับกลาง ให้คลิกต่อไป

9c92662c6a846a5c.png

การจัดสรรและเชื่อมต่อกับ Cloud Shell จะใช้เวลาไม่นาน

9f0e51b578fecce5.png

เครื่องเสมือนนี้โหลดเครื่องมือพัฒนาซอฟต์แวร์ทั้งหมดที่จำเป็นไว้แล้ว โดยมีไดเรกทอรีหลักแบบถาวรขนาด 5 GB และทำงานใน Google Cloud ซึ่งช่วยเพิ่มประสิทธิภาพเครือข่ายและการตรวจสอบสิทธิ์ได้อย่างมาก คุณสามารถทำงานส่วนใหญ่หรือทั้งหมดในโค้ดแล็บนี้ได้ด้วยเบราว์เซอร์

เมื่อเชื่อมต่อกับ Cloud Shell แล้ว คุณควรเห็นว่าคุณได้รับการตรวจสอบสิทธิ์และระบบได้ตั้งค่าโปรเจ็กต์เป็นรหัสโปรเจ็กต์ของคุณ

  1. เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคุณได้รับการตรวจสอบสิทธิ์แล้ว
gcloud auth list

เอาต์พุตของคำสั่ง

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อยืนยันว่าคำสั่ง gcloud รู้จักโปรเจ็กต์ของคุณ
gcloud config list project

เอาต์พุตของคำสั่ง

[core]
project = <PROJECT_ID>

หากไม่ได้ตั้งค่าไว้ คุณตั้งค่าได้ด้วยคำสั่งนี้

gcloud config set project <PROJECT_ID>

เอาต์พุตของคำสั่ง

Updated property [core/project].

3. การตั้งค่าสภาพแวดล้อม

ก่อนที่จะเริ่มใช้ Video Intelligence API ได้ ให้เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อเปิดใช้ API

gcloud services enable videointelligence.googleapis.com

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

Operation "operations/..." finished successfully.

ตอนนี้คุณใช้ Video Intelligence API ได้แล้ว

ไปที่ไดเรกทอรีหลักโดยใช้คำสั่งต่อไปนี้

cd ~

สร้างสภาพแวดล้อมเสมือนของ Python เพื่อแยกการอ้างอิง

virtualenv venv-videointel

เปิดใช้งานสภาพแวดล้อมเสมือน

source venv-videointel/bin/activate

ติดตั้ง IPython และไลบรารีของไคลเอ็นต์ Video Intelligence API โดยทำดังนี้

pip install ipython google-cloud-videointelligence

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

...
Installing collected packages: ..., ipython, google-cloud-videointelligence
Successfully installed ... google-cloud-videointelligence-2.11.0 ...

ตอนนี้คุณพร้อมที่จะใช้ไลบรารีของไคลเอ็นต์ Video Intelligence API แล้ว

ในขั้นตอนถัดไป คุณจะใช้ตัวแปล Python แบบอินเทอร์แอกทีฟที่ชื่อ IPython ซึ่งคุณติดตั้งไว้ในขั้นตอนก่อนหน้า เริ่มเซสชันโดยเรียกใช้ ipython ใน Cloud Shell

ipython

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

Python 3.9.2 (default, Feb 28 2021, 17:03:44)
Type 'copyright', 'credits' or 'license' for more information
IPython 8.12.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]:

4. วิดีโอตัวอย่าง

คุณสามารถใช้ Video Intelligence API เพื่อใส่คำอธิบายประกอบวิดีโอที่จัดเก็บไว้ใน Cloud Storage หรือระบุเป็นไบต์ข้อมูล

ในขั้นตอนถัดไป คุณจะใช้วิดีโอตัวอย่างที่จัดเก็บไว้ใน Cloud Storage คุณดูวิดีโอในเบราว์เซอร์ได้

afe058b29c480d42.png

เตรียมตัว ระวัง ไป!

5. ตรวจหาการเปลี่ยนแปลงช็อต

คุณสามารถใช้ Video Intelligence API เพื่อตรวจหาการเปลี่ยนช็อตในวิดีโอได้ ช็อตคือส่วนของวิดีโอ ซึ่งเป็นชุดเฟรมที่มีความต่อเนื่องของภาพ

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from typing import cast

from google.cloud import videointelligence_v1 as vi


def detect_shot_changes(video_uri: str) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.SHOT_CHANGE_DETECTION]
    request = vi.AnnotateVideoRequest(input_uri=video_uri, features=features)

    print(f'Processing video: "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีที่โค้ดใช้annotate_videoเมธอดไลบรารีไคลเอ็นต์กับพารามิเตอร์ SHOT_CHANGE_DETECTION เพื่อวิเคราะห์วิดีโอและตรวจหาการเปลี่ยนช็อต

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์วิดีโอ

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"

results = detect_shot_changes(video_uri)

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์ภาพจากวิดีโอ

def print_video_shots(results: vi.VideoAnnotationResults):
    shots = results.shot_annotations
    print(f" Video shots: {len(shots)} ".center(40, "-"))
    for i, shot in enumerate(shots):
        t1 = shot.start_time_offset.total_seconds()
        t2 = shot.end_time_offset.total_seconds()
        print(f"{i+1:>3} | {t1:7.3f} | {t2:7.3f}")
        

เรียกใช้ฟังก์ชัน

print_video_shots(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

----------- Video shots: 34 ------------
  1 |   0.000 |  12.880
  2 |  12.920 |  21.680
  3 |  21.720 |  27.880
...
 32 | 135.160 | 138.320
 33 | 138.360 | 146.200
 34 | 146.240 | 162.520

หากดึงเฟรมกลางของแต่ละช็อตออกมาและจัดเรียงเป็นผนังเฟรม คุณจะสร้างข้อมูลสรุปแบบภาพของวิดีโอได้โดยทำดังนี้

25bbffa59f7ed71d.png

สรุป

ในขั้นตอนนี้ คุณสามารถตรวจจับการเปลี่ยนฉากในวิดีโอได้โดยใช้ Video Intelligence API อ่านเพิ่มเติมเกี่ยวกับการตรวจหาการเปลี่ยนช็อต

6. ตรวจหาป้ายกำกับ

คุณสามารถใช้ Video Intelligence API เพื่อตรวจหาป้ายกำกับในวิดีโอได้ ป้ายกำกับจะอธิบายวิดีโอโดยอิงตามเนื้อหาภาพ

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from datetime import timedelta
from typing import Optional, Sequence, cast

from google.cloud import videointelligence_v1 as vi


def detect_labels(
    video_uri: str,
    mode: vi.LabelDetectionMode,
    segments: Optional[Sequence[vi.VideoSegment]] = None,
) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.LABEL_DETECTION]
    config = vi.LabelDetectionConfig(label_detection_mode=mode)
    context = vi.VideoContext(segments=segments, label_detection_config=config)
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีใช้annotate_videoเมธอดไลบรารีของไคลเอ็นต์กับพารามิเตอร์ LABEL_DETECTION เพื่อวิเคราะห์วิดีโอและตรวจหาป้ายกำกับ

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์ 37 วินาทีแรกของวิดีโอ

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"
mode = vi.LabelDetectionMode.SHOT_MODE
segment = vi.VideoSegment(
    start_time_offset=timedelta(seconds=0),
    end_time_offset=timedelta(seconds=37),
)

results = detect_labels(video_uri, mode, [segment])

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์ป้ายกำกับในระดับวิดีโอ

def print_video_labels(results: vi.VideoAnnotationResults):
    labels = sorted_by_first_segment_confidence(results.segment_label_annotations)

    print(f" Video labels: {len(labels)} ".center(80, "-"))
    for label in labels:
        categories = category_entities_to_str(label.category_entities)
        for segment in label.segments:
            confidence = segment.confidence
            t1 = segment.segment.start_time_offset.total_seconds()
            t2 = segment.segment.end_time_offset.total_seconds()
            print(
                f"{confidence:4.0%}",
                f"{t1:7.3f}",
                f"{t2:7.3f}",
                f"{label.entity.description}{categories}",
                sep=" | ",
            )


def sorted_by_first_segment_confidence(
    labels: Sequence[vi.LabelAnnotation],
) -> Sequence[vi.LabelAnnotation]:
    def first_segment_confidence(label: vi.LabelAnnotation) -> float:
        return label.segments[0].confidence

    return sorted(labels, key=first_segment_confidence, reverse=True)


def category_entities_to_str(category_entities: Sequence[vi.Entity]) -> str:
    if not category_entities:
        return ""
    entities = ", ".join([e.description for e in category_entities])
    return f" ({entities})"
    

เรียกใช้ฟังก์ชัน

print_video_labels(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------------------------------- Video labels: 10 -------------------------------
 96% |   0.000 |  36.960 | nature
 74% |   0.000 |  36.960 | vegetation
 59% |   0.000 |  36.960 | tree (plant)
 56% |   0.000 |  36.960 | forest (geographical feature)
 49% |   0.000 |  36.960 | leaf (plant)
 43% |   0.000 |  36.960 | flora (plant)
 38% |   0.000 |  36.960 | nature reserve (geographical feature)
 38% |   0.000 |  36.960 | woodland (forest)
 35% |   0.000 |  36.960 | water resources (water)
 32% |   0.000 |  36.960 | sunlight (light)

ป้ายกำกับระดับวิดีโอเหล่านี้ช่วยให้คุณทราบว่าช่วงต้นของวิดีโอส่วนใหญ่เป็นเรื่องเกี่ยวกับธรรมชาติและพืชพรรณ

เพิ่มฟังก์ชันนี้เพื่อพิมพ์ป้ายกำกับที่ระดับช็อต

def print_shot_labels(results: vi.VideoAnnotationResults):
    labels = sorted_by_first_segment_start_and_confidence(
        results.shot_label_annotations
    )

    print(f" Shot labels: {len(labels)} ".center(80, "-"))
    for label in labels:
        categories = category_entities_to_str(label.category_entities)
        print(f"{label.entity.description}{categories}")
        for segment in label.segments:
            confidence = segment.confidence
            t1 = segment.segment.start_time_offset.total_seconds()
            t2 = segment.segment.end_time_offset.total_seconds()
            print(f"{confidence:4.0%} | {t1:7.3f} | {t2:7.3f}")


def sorted_by_first_segment_start_and_confidence(
    labels: Sequence[vi.LabelAnnotation],
) -> Sequence[vi.LabelAnnotation]:
    def first_segment_start_and_confidence(label: vi.LabelAnnotation):
        first_segment = label.segments[0]
        ms = first_segment.segment.start_time_offset.total_seconds()
        return (ms, -first_segment.confidence)

    return sorted(labels, key=first_segment_start_and_confidence)
    

เรียกใช้ฟังก์ชัน

print_shot_labels(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------------------------------- Shot labels: 29 --------------------------------
planet (astronomical object)
 83% |   0.000 |  12.880
earth (planet)
 53% |   0.000 |  12.880
water resources (water)
 43% |   0.000 |  12.880
aerial photography (photography)
 43% |   0.000 |  12.880
vegetation
 32% |   0.000 |  12.880
 92% |  12.920 |  21.680
 83% |  21.720 |  27.880
 77% |  27.920 |  31.800
 76% |  31.840 |  34.720
...
butterfly (insect, animal)
 84% |  34.760 |  36.960
...

ป้ายกำกับระดับช็อตเหล่านี้ช่วยให้คุณทราบว่าวิดีโอเริ่มต้นด้วยช็อตของดาวเคราะห์ (น่าจะเป็นโลก) มีผีเสื้อในช็อต 34.760-36.960s...

สรุป

ในขั้นตอนนี้ คุณสามารถตรวจหาป้ายกำกับในวิดีโอได้โดยใช้ Video Intelligence API อ่านเพิ่มเติมเกี่ยวกับการตรวจหาป้ายกำกับ

7. ตรวจหาเนื้อหาที่อาจไม่เหมาะสม

คุณสามารถใช้ Video Intelligence API เพื่อตรวจหาเนื้อหาโจ่งแจ้งในวิดีโอได้ เนื้อหาโจ่งแจ้งคือเนื้อหาสำหรับผู้ใหญ่ที่ไม่เหมาะสมสำหรับผู้ที่มีอายุต่ำกว่า 18 ปีโดยทั่วไป ซึ่งรวมถึงแต่ไม่จำกัดเพียงภาพเปลือย กิจกรรมทางเพศ และสื่อลามก การตรวจหาจะดำเนินการโดยอิงตามสัญญาณภาพต่อเฟรมเท่านั้น (ไม่ได้ใช้เสียง) การตอบกลับจะมีค่าความน่าจะเป็นตั้งแต่ VERY_UNLIKELY ถึง VERY_LIKELY

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from datetime import timedelta
from typing import Optional, Sequence, cast

from google.cloud import videointelligence_v1 as vi


def detect_explicit_content(
    video_uri: str,
    segments: Optional[Sequence[vi.VideoSegment]] = None,
) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.EXPLICIT_CONTENT_DETECTION]
    context = vi.VideoContext(segments=segments)
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีใช้annotate_videoเมธอดไลบรารีไคลเอ็นต์กับพารามิเตอร์ EXPLICIT_CONTENT_DETECTION เพื่อวิเคราะห์วิดีโอและตรวจหาเนื้อหาโจ่งแจ้ง

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์ 10 วินาทีแรกของวิดีโอ

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"
segment = vi.VideoSegment(
    start_time_offset=timedelta(seconds=0),
    end_time_offset=timedelta(seconds=10),
)

results = detect_explicit_content(video_uri, [segment])

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์จำนวนความน่าจะเป็นต่างๆ

def print_explicit_content(results: vi.VideoAnnotationResults):
    from collections import Counter

    frames = results.explicit_annotation.frames
    likelihood_counts = Counter([f.pornography_likelihood for f in frames])

    print(f" Explicit content frames: {len(frames)} ".center(40, "-"))
    for likelihood in vi.Likelihood:
        print(f"{likelihood.name:<22}: {likelihood_counts[likelihood]:>3}")
        

เรียกใช้ฟังก์ชัน

print_explicit_content(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

----- Explicit content frames: 10 ------
LIKELIHOOD_UNSPECIFIED:   0
VERY_UNLIKELY         :  10
UNLIKELY              :   0
POSSIBLE              :   0
LIKELY                :   0
VERY_LIKELY           :   0

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายละเอียดเฟรม

def print_frames(results: vi.VideoAnnotationResults, likelihood: vi.Likelihood):
    frames = results.explicit_annotation.frames
    frames = [f for f in frames if f.pornography_likelihood == likelihood]

    print(f" {likelihood.name} frames: {len(frames)} ".center(40, "-"))
    for frame in frames:
        print(frame.time_offset)
        

เรียกใช้ฟังก์ชัน

print_frames(results, vi.Likelihood.VERY_UNLIKELY)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------- VERY_UNLIKELY frames: 10 -------
0:00:00.365992
0:00:01.279206
0:00:02.268336
0:00:03.289253
0:00:04.400163
0:00:05.291547
0:00:06.449558
0:00:07.452751
0:00:08.577405
0:00:09.554514

สรุป

ในขั้นตอนนี้ คุณสามารถตรวจหาเนื้อหาโจ่งแจ้งในวิดีโอโดยใช้ Video Intelligence API อ่านเพิ่มเติมเกี่ยวกับการตรวจหาเนื้อหาโจ่งแจ้ง

8. ถอดเสียงคำพูด

คุณใช้ Video Intelligence API เพื่อถอดเสียงพูดในวิดีโอเป็นข้อความได้

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from datetime import timedelta
from typing import Optional, Sequence, cast

from google.cloud import videointelligence_v1 as vi


def transcribe_speech(
    video_uri: str,
    language_code: str,
    segments: Optional[Sequence[vi.VideoSegment]] = None,
) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.SPEECH_TRANSCRIPTION]
    config = vi.SpeechTranscriptionConfig(
        language_code=language_code,
        enable_automatic_punctuation=True,
    )
    context = vi.VideoContext(
        segments=segments,
        speech_transcription_config=config,
    )
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีที่โค้ดใช้annotate_videoเมธอดไลบรารีของไคลเอ็นต์กับพารามิเตอร์ SPEECH_TRANSCRIPTION เพื่อวิเคราะห์วิดีโอและถอดเสียงพูด

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์วิดีโอตั้งแต่ 55 ถึง 80 วินาที

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"
language_code = "en-GB"
segment = vi.VideoSegment(
    start_time_offset=timedelta(seconds=55),
    end_time_offset=timedelta(seconds=80),
)

results = transcribe_speech(video_uri, language_code, [segment])

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์คำพูดที่ถอดเสียง

def print_video_speech(results: vi.VideoAnnotationResults, min_confidence: float = 0.8):
    def keep_transcription(transcription: vi.SpeechTranscription) -> bool:
        return min_confidence <= transcription.alternatives[0].confidence

    transcriptions = results.speech_transcriptions
    transcriptions = [t for t in transcriptions if keep_transcription(t)]

    print(f" Speech transcriptions: {len(transcriptions)} ".center(80, "-"))
    for transcription in transcriptions:
        first_alternative = transcription.alternatives[0]
        confidence = first_alternative.confidence
        transcript = first_alternative.transcript
        print(f" {confidence:4.0%} | {transcript.strip()}")
        

เรียกใช้ฟังก์ชัน

print_video_speech(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

--------------------------- Speech transcriptions: 2 ---------------------------
  91% | I was keenly aware of secret movements in the trees.
  92% | I looked into his large and lustrous eyes. They seem somehow to express his entire personality.

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายการคำที่ตรวจพบและการประทับเวลาของคำเหล่านั้น

def print_word_timestamps(
    results: vi.VideoAnnotationResults,
    min_confidence: float = 0.8,
):
    def keep_transcription(transcription: vi.SpeechTranscription) -> bool:
        return min_confidence <= transcription.alternatives[0].confidence

    transcriptions = results.speech_transcriptions
    transcriptions = [t for t in transcriptions if keep_transcription(t)]

    print(" Word timestamps ".center(80, "-"))
    for transcription in transcriptions:
        first_alternative = transcription.alternatives[0]
        confidence = first_alternative.confidence
        for word in first_alternative.words:
            t1 = word.start_time.total_seconds()
            t2 = word.end_time.total_seconds()
            word = word.word
            print(f"{confidence:4.0%} | {t1:7.3f} | {t2:7.3f} | {word}")
            

เรียกใช้ฟังก์ชัน

print_word_timestamps(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------------------------------- Word timestamps --------------------------------
 93% |  55.000 |  55.700 | I
 93% |  55.700 |  55.900 | was
 93% |  55.900 |  56.300 | keenly
 93% |  56.300 |  56.700 | aware
 93% |  56.700 |  56.900 | of
...
 94% |  76.900 |  77.400 | express
 94% |  77.400 |  77.600 | his
 94% |  77.600 |  78.200 | entire
 94% |  78.200 |  78.500 | personality.

สรุป

ในขั้นตอนนี้ คุณสามารถถอดเสียงพูดในวิดีโอโดยใช้ Video Intelligence API อ่านเพิ่มเติมเกี่ยวกับการถอดเสียง

9. ตรวจจับและติดตามข้อความ

คุณสามารถใช้ Video Intelligence API เพื่อตรวจจับและติดตามข้อความในวิดีโอได้

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from datetime import timedelta
from typing import Optional, Sequence, cast

from google.cloud import videointelligence_v1 as vi


def detect_text(
    video_uri: str,
    language_hints: Optional[Sequence[str]] = None,
    segments: Optional[Sequence[vi.VideoSegment]] = None,
) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.TEXT_DETECTION]
    config = vi.TextDetectionConfig(
        language_hints=language_hints,
    )
    context = vi.VideoContext(
        segments=segments,
        text_detection_config=config,
    )
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีที่โค้ดใช้annotate_videoเมธอดไลบรารีของไคลเอ็นต์กับพารามิเตอร์ TEXT_DETECTION เพื่อวิเคราะห์วิดีโอและตรวจหาข้อความ

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์วิดีโอตั้งแต่ 13 ถึง 27 วินาที

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"
segment = vi.VideoSegment(
    start_time_offset=timedelta(seconds=13),
    end_time_offset=timedelta(seconds=27),
)

results = detect_text(video_uri, segments=[segment])

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์ข้อความที่ตรวจพบ

def print_video_text(results: vi.VideoAnnotationResults, min_frames: int = 15):
    annotations = sorted_by_first_segment_end(results.text_annotations)

    print(" Detected text ".center(80, "-"))
    for annotation in annotations:
        for text_segment in annotation.segments:
            frames = len(text_segment.frames)
            if frames < min_frames:
                continue
            text = annotation.text
            confidence = text_segment.confidence
            start = text_segment.segment.start_time_offset
            seconds = segment_seconds(text_segment.segment)
            print(text)
            print(f"  {confidence:4.0%} | {start} + {seconds:.1f}s | {frames} fr.")


def sorted_by_first_segment_end(
    annotations: Sequence[vi.TextAnnotation],
) -> Sequence[vi.TextAnnotation]:
    def first_segment_end(annotation: vi.TextAnnotation) -> int:
        return annotation.segments[0].segment.end_time_offset.total_seconds()

    return sorted(annotations, key=first_segment_end)


def segment_seconds(segment: vi.VideoSegment) -> float:
    t1 = segment.start_time_offset.total_seconds()
    t2 = segment.end_time_offset.total_seconds()
    return t2 - t1
    

เรียกใช้ฟังก์ชัน

print_video_text(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

-------------------------------- Detected text ---------------------------------
GOMBE NATIONAL PARK
   99% | 0:00:15.760000 + 1.7s | 15 fr.
TANZANIA
  100% | 0:00:15.760000 + 4.8s | 39 fr.
With words and narration by
  100% | 0:00:23.200000 + 3.6s | 31 fr.
Jane Goodall
   99% | 0:00:23.080000 + 3.8s | 33 fr.

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายการกรอบข้อความและกรอบล้อมรอบที่ตรวจพบ

def print_text_frames(results: vi.VideoAnnotationResults, contained_text: str):
    # Vertex order: top-left, top-right, bottom-right, bottom-left
    def box_top_left(box: vi.NormalizedBoundingPoly) -> str:
        tl = box.vertices[0]
        return f"({tl.x:.5f}, {tl.y:.5f})"

    def box_bottom_right(box: vi.NormalizedBoundingPoly) -> str:
        br = box.vertices[2]
        return f"({br.x:.5f}, {br.y:.5f})"

    annotations = results.text_annotations
    annotations = [a for a in annotations if contained_text in a.text]
    for annotation in annotations:
        print(f" {annotation.text} ".center(80, "-"))
        for text_segment in annotation.segments:
            for frame in text_segment.frames:
                frame_ms = frame.time_offset.total_seconds()
                box = frame.rotated_bounding_box
                print(
                    f"{frame_ms:>7.3f}",
                    box_top_left(box),
                    box_bottom_right(box),
                    sep=" | ",
                )
                

เรียกใช้ฟังก์ชันเพื่อตรวจสอบว่าเฟรมใดแสดงชื่อผู้บรรยาย

contained_text = "Goodall"
print_text_frames(results, contained_text)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

--------------------------------- Jane Goodall ---------------------------------
 23.080 | (0.39922, 0.49861) | (0.62752, 0.55888)
 23.200 | (0.38750, 0.49028) | (0.62692, 0.56306)
...
 26.800 | (0.36016, 0.49583) | (0.61094, 0.56048)
 26.920 | (0.45859, 0.49583) | (0.60365, 0.56174)

หากวาดกรอบล้อมรอบบนเฟรมที่เกี่ยวข้อง คุณจะได้ผลลัพธ์ดังนี้

7e530d3d25f2f40e.gif

สรุป

ในขั้นตอนนี้ คุณสามารถตรวจหาและติดตามข้อความในวิดีโอได้โดยใช้ Video Intelligence API อ่านเพิ่มเติมเกี่ยวกับการตรวจหาและติดตามข้อความ

10. ตรวจจับและติดตามวัตถุ

คุณสามารถใช้ Video Intelligence API เพื่อตรวจจับและติดตามออบเจ็กต์ในวิดีโอได้

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from datetime import timedelta
from typing import Optional, Sequence, cast

from google.cloud import videointelligence_v1 as vi


def track_objects(
    video_uri: str, segments: Optional[Sequence[vi.VideoSegment]] = None
) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.OBJECT_TRACKING]
    context = vi.VideoContext(segments=segments)
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีที่โค้ดใช้annotate_videoเมธอดไลบรารีไคลเอ็นต์กับพารามิเตอร์ OBJECT_TRACKING เพื่อวิเคราะห์วิดีโอและตรวจจับออบเจ็กต์

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์วิดีโอตั้งแต่ 98 ถึง 112 วินาที

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"
segment = vi.VideoSegment(
    start_time_offset=timedelta(seconds=98),
    end_time_offset=timedelta(seconds=112),
)

results = track_objects(video_uri, [segment])

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายการออบเจ็กต์ที่ตรวจพบ

def print_detected_objects(
    results: vi.VideoAnnotationResults,
    min_confidence: float = 0.7,
):
    annotations = results.object_annotations
    annotations = [a for a in annotations if min_confidence <= a.confidence]

    print(
        f" Detected objects: {len(annotations)}"
        f" ({min_confidence:.0%} <= confidence) ".center(80, "-")
    )
    for annotation in annotations:
        entity = annotation.entity
        description = entity.description
        entity_id = entity.entity_id
        confidence = annotation.confidence
        t1 = annotation.segment.start_time_offset.total_seconds()
        t2 = annotation.segment.end_time_offset.total_seconds()
        frames = len(annotation.frames)
        print(
            f"{description:<22}",
            f"{entity_id:<10}",
            f"{confidence:4.0%}",
            f"{t1:>7.3f}",
            f"{t2:>7.3f}",
            f"{frames:>2} fr.",
            sep=" | ",
        )
        

เรียกใช้ฟังก์ชัน

print_detected_objects(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------------------- Detected objects: 3 (70% <= confidence) --------------------
insect                 | /m/03vt0   |  87% |  98.840 | 101.720 | 25 fr.
insect                 | /m/03vt0   |  71% | 108.440 | 111.080 | 23 fr.
butterfly              | /m/0cyf8   |  91% | 111.200 | 111.920 |  7 fr.

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายการเฟรมออบเจ็กต์ที่ตรวจพบและกรอบล้อมรอบ

def print_object_frames(
    results: vi.VideoAnnotationResults,
    entity_id: str,
    min_confidence: float = 0.7,
):
    def keep_annotation(annotation: vi.ObjectTrackingAnnotation) -> bool:
        return (
            annotation.entity.entity_id == entity_id
            and min_confidence <= annotation.confidence
        )

    annotations = results.object_annotations
    annotations = [a for a in annotations if keep_annotation(a)]
    for annotation in annotations:
        description = annotation.entity.description
        confidence = annotation.confidence
        print(
            f" {description},"
            f" confidence: {confidence:.0%},"
            f" frames: {len(annotation.frames)} ".center(80, "-")
        )
        for frame in annotation.frames:
            t = frame.time_offset.total_seconds()
            box = frame.normalized_bounding_box
            print(
                f"{t:>7.3f}",
                f"({box.left:.5f}, {box.top:.5f})",
                f"({box.right:.5f}, {box.bottom:.5f})",
                sep=" | ",
            )
            

เรียกใช้ฟังก์ชันด้วยรหัสเอนทิตีสำหรับแมลง

insect_entity_id = "/m/03vt0"
print_object_frames(results, insect_entity_id)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

--------------------- insect, confidence: 87%, frames: 25 ----------------------
 98.840 | (0.49327, 0.19617) | (0.69905, 0.69633)
 98.960 | (0.49559, 0.19308) | (0.70631, 0.69671)
...
101.600 | (0.46668, 0.19776) | (0.76619, 0.69371)
101.720 | (0.46805, 0.20053) | (0.76447, 0.68703)
--------------------- insect, confidence: 71%, frames: 23 ----------------------
108.440 | (0.47343, 0.10694) | (0.63821, 0.98332)
108.560 | (0.46960, 0.10206) | (0.63033, 0.98285)
...
110.960 | (0.49466, 0.05102) | (0.65941, 0.99357)
111.080 | (0.49572, 0.04728) | (0.65762, 0.99868)

หากวาดกรอบล้อมรอบบนเฟรมที่เกี่ยวข้อง คุณจะได้ผลลัพธ์ดังนี้

8f5796f6e73d1a46.gif

c195a2dca4573f95.gif

สรุป

ในขั้นตอนนี้ คุณสามารถตรวจจับและติดตามออบเจ็กต์ในวิดีโอได้โดยใช้ Video Intelligence API คุณอ่านข้อมูลเพิ่มเติมเกี่ยวกับการตรวจหาและติดตามออบเจ็กต์ได้

11. ตรวจจับและติดตามโลโก้

คุณสามารถใช้ Video Intelligence API เพื่อตรวจจับและติดตามโลโก้ในวิดีโอได้ ตรวจจับแบรนด์และโลโก้ได้กว่า 100,000 รายการ

คัดลอกโค้ดต่อไปนี้ลงในเซสชัน IPython

from datetime import timedelta
from typing import Optional, Sequence, cast

from google.cloud import videointelligence_v1 as vi


def detect_logos(
    video_uri: str, segments: Optional[Sequence[vi.VideoSegment]] = None
) -> vi.VideoAnnotationResults:
    video_client = vi.VideoIntelligenceServiceClient()
    features = [vi.Feature.LOGO_RECOGNITION]
    context = vi.VideoContext(segments=segments)
    request = vi.AnnotateVideoRequest(
        input_uri=video_uri,
        features=features,
        video_context=context,
    )

    print(f'Processing video "{video_uri}"...')
    operation = video_client.annotate_video(request)

    # Wait for operation to complete
    response = cast(vi.AnnotateVideoResponse, operation.result())
    # A single video is processed
    results = response.annotation_results[0]

    return results
    

โปรดใช้เวลาสักครู่เพื่อศึกษาโค้ดและดูวิธีที่โค้ดใช้annotate_videoเมธอดไลบรารีของไคลเอ็นต์กับพารามิเตอร์ LOGO_RECOGNITION เพื่อวิเคราะห์วิดีโอและตรวจหาโลโก้

เรียกใช้ฟังก์ชันเพื่อวิเคราะห์ลำดับก่อนสุดท้ายของวิดีโอ

video_uri = "gs://cloud-samples-data/video/JaneGoodall.mp4"
segment = vi.VideoSegment(
    start_time_offset=timedelta(seconds=146),
    end_time_offset=timedelta(seconds=156),
)

results = detect_logos(video_uri, [segment])

รอให้ระบบประมวลผลวิดีโอ

Processing video: "gs://cloud-samples-data/video/JaneGoodall.mp4"...

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายการโลโก้ที่ตรวจพบ

def print_detected_logos(results: vi.VideoAnnotationResults):
    annotations = results.logo_recognition_annotations

    print(f" Detected logos: {len(annotations)} ".center(80, "-"))
    for annotation in annotations:
        entity = annotation.entity
        entity_id = entity.entity_id
        description = entity.description
        for track in annotation.tracks:
            confidence = track.confidence
            t1 = track.segment.start_time_offset.total_seconds()
            t2 = track.segment.end_time_offset.total_seconds()
            logo_frames = len(track.timestamped_objects)
            print(
                f"{confidence:4.0%}",
                f"{t1:>7.3f}",
                f"{t2:>7.3f}",
                f"{logo_frames:>3} fr.",
                f"{entity_id:<15}",
                f"{description}",
                sep=" | ",
            )
            

เรียกใช้ฟังก์ชัน

print_detected_logos(results)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------------------------------ Detected logos: 1 -------------------------------
 92% | 150.680 | 155.720 |  43 fr. | /m/055t58       | Google Maps

เพิ่มฟังก์ชันนี้เพื่อพิมพ์รายการเฟรมโลโก้และกรอบล้อมรอบที่ตรวจพบ

def print_logo_frames(results: vi.VideoAnnotationResults, entity_id: str):
    def keep_annotation(annotation: vi.LogoRecognitionAnnotation) -> bool:
        return annotation.entity.entity_id == entity_id

    annotations = results.logo_recognition_annotations
    annotations = [a for a in annotations if keep_annotation(a)]
    for annotation in annotations:
        description = annotation.entity.description
        for track in annotation.tracks:
            confidence = track.confidence
            print(
                f" {description},"
                f" confidence: {confidence:.0%},"
                f" frames: {len(track.timestamped_objects)} ".center(80, "-")
            )
            for timestamped_object in track.timestamped_objects:
                t = timestamped_object.time_offset.total_seconds()
                box = timestamped_object.normalized_bounding_box
                print(
                    f"{t:>7.3f}",
                    f"({box.left:.5f}, {box.top:.5f})",
                    f"({box.right:.5f}, {box.bottom:.5f})",
                    sep=" | ",
                )
                

เรียกใช้ฟังก์ชันด้วยรหัสเอนทิตีโลโก้ Google Maps ดังนี้

maps_entity_id = "/m/055t58"
print_logo_frames(results, maps_entity_id)

คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้

------------------- Google Maps, confidence: 92%, frames: 43 -------------------
150.680 | (0.42024, 0.28633) | (0.58192, 0.64220)
150.800 | (0.41713, 0.27822) | (0.58318, 0.63556)
...
155.600 | (0.41775, 0.27701) | (0.58372, 0.63986)
155.720 | (0.41688, 0.28005) | (0.58335, 0.63954)

หากวาดกรอบล้อมรอบบนเฟรมที่เกี่ยวข้อง คุณจะได้ผลลัพธ์ดังนี้

554743aff6d8824c.gif

สรุป

ในขั้นตอนนี้ คุณสามารถตรวจหาและติดตามโลโก้ในวิดีโอได้โดยใช้ Video Intelligence API อ่านเพิ่มเติมเกี่ยวกับการตรวจหาและติดตามโลโก้

12. ตรวจหาฟีเจอร์หลายรายการ

คุณสามารถส่งคำขอประเภทต่อไปนี้เพื่อรับข้อมูลเชิงลึกทั้งหมดพร้อมกัน

from google.cloud import videointelligence_v1 as vi

video_client = vi.VideoIntelligenceServiceClient()
video_uri = "gs://..."
features = [
    vi.Feature.SHOT_CHANGE_DETECTION,
    vi.Feature.LABEL_DETECTION,
    vi.Feature.EXPLICIT_CONTENT_DETECTION,
    vi.Feature.SPEECH_TRANSCRIPTION,
    vi.Feature.TEXT_DETECTION,
    vi.Feature.OBJECT_TRACKING,
    vi.Feature.LOGO_RECOGNITION,
    vi.Feature.FACE_DETECTION,  # NEW
    vi.Feature.PERSON_DETECTION,  # NEW
]
context = vi.VideoContext(
    segments=...,
    shot_change_detection_config=...,
    label_detection_config=...,
    explicit_content_detection_config=...,
    speech_transcription_config=...,
    text_detection_config=...,
    object_tracking_config=...,
    face_detection_config=...,  # NEW
    person_detection_config=...,  # NEW
)
request = vi.AnnotateVideoRequest(
    input_uri=video_uri,
    features=features,
    video_context=context,
)

# video_client.annotate_video(request)

13. ยินดีด้วย

cfaa6ffa7bc5ca70.png

คุณได้เรียนรู้วิธีใช้ Video Intelligence API โดยใช้ Python แล้ว

ล้างข้อมูล

หากต้องการล้างข้อมูลสภาพแวดล้อมในการพัฒนา ให้ทำดังนี้จาก Cloud Shell

  • หากยังอยู่ในเซสชัน IPython ให้กลับไปที่เชลล์โดยทำดังนี้ exit
  • หยุดใช้สภาพแวดล้อมเสมือนของ Python: deactivate
  • ลบโฟลเดอร์สภาพแวดล้อมเสมือน cd ~ ; rm -rf ./venv-videointel

หากต้องการลบโปรเจ็กต์ Google Cloud ให้ทำดังนี้จาก Cloud Shell

  • ดึงรหัสโปรเจ็กต์ปัจจุบัน: PROJECT_ID=$(gcloud config get-value core/project)
  • โปรดตรวจสอบว่าโปรเจ็กต์ที่คุณต้องการลบคือ echo $PROJECT_ID
  • ลบโปรเจ็กต์: gcloud projects delete $PROJECT_ID

ดูข้อมูลเพิ่มเติม

ใบอนุญาต

ผลงานนี้ได้รับอนุญาตภายใต้สัญญาอนุญาตครีเอทีฟคอมมอนส์สำหรับยอมรับสิทธิของผู้สร้าง (Creative Commons Attribution License) 2.0 แบบทั่วไป