<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nookforge Studio</title>
    <description>The latest articles on DEV Community by Nookforge Studio (@nookforgestudio).</description>
    <link>https://dev.to/nookforgestudio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4221173%2Fc9d2d59b-15c7-4efc-994e-19357b2b9915.png</url>
      <title>DEV Community: Nookforge Studio</title>
      <link>https://dev.to/nookforgestudio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nookforgestudio"/>
    <language>en</language>
    <item>
      <title>Turning a 5-minute talk into 7 images for an LLM: picking one sharp frame per scene</title>
      <dc:creator>Nookforge Studio</dc:creator>
      <pubDate>Sun, 01 Nov 2026 22:03:14 +0000</pubDate>
      <link>https://dev.to/nookforgestudio/turning-a-5-minute-talk-into-7-images-for-an-llm-picking-one-sharp-frame-per-scene-3k43</link>
      <guid>https://dev.to/nookforgestudio/turning-a-5-minute-talk-into-7-images-for-an-llm-picking-one-sharp-frame-per-scene-3k43</guid>
      <description>&lt;p&gt;If you send a video to a vision LLM, the usual first step is to sample a frame every few seconds. On a 4:44 conference talk, one frame every 10 seconds gives you 28 images. The talk has 7 slides. You pay for 21 repeats, and some of the 28 catch a slide halfway through a transition or before its last bullet appears.&lt;/p&gt;

&lt;p&gt;What you actually want is one sharp frame per scene: per shot in a film, per slide in a talk. Here is how I built that, with numbers from real videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One cheap scan
&lt;/h2&gt;

&lt;p&gt;Decode the video once, at 320 px wide in grayscale, and keep 0.5 to 4 samples per second depending on length. For each sample, compute three numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sharpness&lt;/strong&gt;: variance of the Laplacian.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change&lt;/strong&gt;: mean absolute difference between this sample and the previous one, on a 64×36 thumbnail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flatness&lt;/strong&gt;: whether the frame is nearly uniform (fades, black screens).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pass is the only full decode of the video, so it sets the cost. Two ffmpeg flags help a lot on H.264: &lt;code&gt;-skip_frame noref -skip_loop_filter all&lt;/code&gt; made the scan 1.85× faster, and since we only need a few samples per second at 320 px, nothing is lost. (VP9 and Theora ignore them.)&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Find scene cuts with an adaptive threshold
&lt;/h2&gt;

&lt;p&gt;A fixed threshold fails in both directions. Hard cuts in a film produce big jumps. Slide changes in a filmed talk jump by as little as 0.017, because the slide only fills part of the frame next to the speaker's camera. Camera motion, meanwhile, produces a steady stream of moderate differences for seconds at a time, so a low fixed threshold would cut on every pan.&lt;/p&gt;

&lt;p&gt;So a cut is either a hard jump, or a jump that is large relative to the recent baseline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_cuts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hard&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_scene&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Indices where a new scene starts: hard cuts, or jumps far above the recent baseline.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fps&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;min_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_scene&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;cuts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;min_len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;win&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;hard&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.004&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cuts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a static slide deck the baseline is about 0, so a 0.017 jump stands out. During a pan the baseline is high, so motion doesn't trigger cuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Pick the best frame of each scene
&lt;/h2&gt;

&lt;p&gt;Inside each scene, skip the first and last 15% (transitions live there), skip flat frames, and score the rest by sharpness divided by local motion:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;score = sharpness / (1 + 20 × motion)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For slides this naturally picks the fully built slide, because more text means more edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Drop blur and duplicates
&lt;/h2&gt;

&lt;p&gt;A pick is blurry if its sharpness is under 25% of the video's 75th-percentile sharpness. That catches motion blur and focus pulls without penalizing soft-looking films.&lt;/p&gt;

&lt;p&gt;Duplicates were the interesting bug. I started with a 64-bit difference hash (a 9×8 thumbnail), the usual choice. On the talk it merged different slides: at 9×8, every slide is "white box on the left, dark webcam on the right." What works is comparing the 64×36 thumbnails directly and calling two frames duplicates only when under 1% of pixels changed noticeably. Different slides pass, and a shot the video cuts back to is kept once.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Grab the full-resolution frames in one pass
&lt;/h2&gt;

&lt;p&gt;Starting one ffmpeg process per frame cost about a second per frame, mostly process startup plus decoding forward from the previous keyframe. One decoding pass with a select filter was 5.7× faster on a 70-second clip (21 frames in 3.7 s instead of 21 s).&lt;/p&gt;

&lt;p&gt;My first version selected by frame number. It broke on an Ogg file that decoded 1,113 frames where 1,254 were expected, so the last targets never existed. Selecting by timestamp, the first frame at or after each target time, works on variable frame rates and dropped frames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select='gte(t\,9.8)*lt(prev_pts*TB\,9.8)+gte(t\,16.8)*lt(prev_pts*TB\,16.8)+...'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For very long videos, seeking to each frame is still cheaper than decoding everything again, so the code picks whichever is faster using the measured scan time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Fixed sampling uses ffmpeg's &lt;code&gt;fps=1/10&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;Every 10 s&lt;/th&gt;
&lt;th&gt;Smart keyframes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4:44 talk with slides (high sensitivity)&lt;/td&gt;
&lt;td&gt;28 frames, 7 distinct slides&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7 frames, one per slide, all bullets visible&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:14 film, 1080p (Tears of Steel)&lt;/td&gt;
&lt;td&gt;73 frames&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;159 scenes, 24 blurry picks dropped, best 100 kept&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32 s trailer (Big Buck Bunny)&lt;/td&gt;
&lt;td&gt;3 frames&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16 frames, one per shot&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;I packaged this as &lt;a href="https://apify.com/nookforgestudio/video-keyframe-extractor" rel="noopener noreferrer"&gt;Smart Video Keyframes&lt;/a&gt; on Apify. Pass video file URLs and get keyframes with timestamps, a contact sheet and, optionally, the on-screen text of each frame. Two ready-made examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apify.com/nookforgestudio/video-keyframe-extractor/examples/conference-talk-to-slide-frames" rel="noopener noreferrer"&gt;Turn a conference talk into one frame per slide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apify.com/nookforgestudio/video-keyframe-extractor/examples/best-frame-per-scene" rel="noopener noreferrer"&gt;Pick the best frame of every scene&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Videos via Wikimedia Commons: "Research and Privacy on Wikipedia" lightning talk by Eli Asikin-Garmager (CC0), used for the cover; Tears of Steel and Big Buck Bunny by the Blender Foundation (CC BY 3.0).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>computervision</category>
      <category>python</category>
      <category>ffmpeg</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why object detectors miss small objects in big photos, and how tiling fixes it</title>
      <dc:creator>Nookforge Studio</dc:creator>
      <pubDate>Sun, 01 Nov 2026 20:45:16 +0000</pubDate>
      <link>https://dev.to/nookforgestudio/why-object-detectors-miss-small-objects-in-big-photos-and-how-tiling-fixes-it-22jm</link>
      <guid>https://dev.to/nookforgestudio/why-object-detectors-miss-small-objects-in-big-photos-and-how-tiling-fixes-it-22jm</guid>
      <description>&lt;p&gt;Point an off-the-shelf object detector at a drone photo of a marina and it will tell you there are five boats. There are dozens. The model isn't bad; the pipeline throws the boats away before the model ever sees them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The resize problem
&lt;/h2&gt;

&lt;p&gt;Most real-time detectors, YOLO and its relatives included, are trained and run at around 640×640 pixels. A 3613×2400 aerial photo gets shrunk by a factor of about 5.6 to fit. A boat that was 30 pixels long becomes about 5 pixels long, which is below what the network can reliably pick out. The same thing happens to background people in crowd shots and to cars in parking lot photos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sliced inference
&lt;/h2&gt;

&lt;p&gt;The fix is to stop shrinking the image and look at it in pieces instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cut the image into overlapping tiles at full resolution, 640 px with about 20% overlap.&lt;/li&gt;
&lt;li&gt;Run the detector on every tile, plus one pass on the whole downscaled image so large objects that span tiles are still found.&lt;/li&gt;
&lt;li&gt;Shift each tile's boxes back into full-image coordinates.&lt;/li&gt;
&lt;li&gt;Merge duplicates. Plain IoU-based non-max suppression is not enough, because an object cut by a tile edge produces a partial box that barely overlaps the full one. Matching on intersection-over-smaller-box as well as IoU joins those fragments into one object.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;I ran the same YOLOX-S model on three public photos from Wikimedia Commons, once as a single pass and once tiled:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Photo&lt;/th&gt;
&lt;th&gt;Single pass&lt;/th&gt;
&lt;th&gt;Tiled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aerial marina, 3613×2400&lt;/td&gt;
&lt;td&gt;5 boats&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91 boats&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Festival crowd, 3586×2981&lt;/td&gt;
&lt;td&gt;18 people&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;105 people&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parking lot, 2304×2304&lt;/td&gt;
&lt;td&gt;18 cars&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;31 cars&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are detector outputs, not hand-checked counts, but the gap is the point: most of what the single pass missed was simply too small after resizing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off
&lt;/h2&gt;

&lt;p&gt;Tiling costs compute. The marina photo needs 35 tiles, so it runs 36 inferences instead of one. Two knobs keep that under control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only tile images that are meaningfully larger than the model's input. I start at about 1.5× the tile size.&lt;/li&gt;
&lt;li&gt;Cap the number of tiles per image, and enlarge the tiles when the cap is hit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When tiling isn't enough
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Very dense crowds with heavily overlapping heads need density-estimation models, not detectors.&lt;/li&gt;
&lt;li&gt;Straight-down satellite views of tiny objects need a model trained on overhead imagery; COCO-trained detectors expect side and oblique views.&lt;/li&gt;
&lt;li&gt;Tiling finds more objects, and more false positives. Raise the confidence threshold or filter to the classes you need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to build this yourself, &lt;a href="https://github.com/obss/sahi" rel="noopener noreferrer"&gt;SAHI&lt;/a&gt; is a good Python starting point.&lt;/p&gt;

&lt;p&gt;If you'd rather pass in image URLs and get counts, boxes and annotated previews back, I packaged this pipeline as &lt;a href="https://apify.com/nookforgestudio/object-counter" rel="noopener noreferrer"&gt;Object Counter&lt;/a&gt;, priced per image. You can run the same three photos from this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apify.com/nookforgestudio/object-counter/examples/count-boats-in-marina-aerial-photos" rel="noopener noreferrer"&gt;Count boats in marina and aerial photos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apify.com/nookforgestudio/object-counter/examples/count-people-in-crowd-photos" rel="noopener noreferrer"&gt;Count people in crowd and event photos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apify.com/nookforgestudio/object-counter/examples/count-cars-in-parking-lot-photos" rel="noopener noreferrer"&gt;Count cars in parking lot photos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Photo credits, all via Wikimedia Commons: &lt;a href="https://commons.wikimedia.org/wiki/File:Marina_aerial_southbasin.jpg" rel="noopener noreferrer"&gt;aerial marina&lt;/a&gt; by GPlantam, CC BY-SA 4.0; &lt;a href="https://commons.wikimedia.org/wiki/File:Festival_crowds_-_geograph.org.uk_-_8342370.jpg" rel="noopener noreferrer"&gt;festival crowd&lt;/a&gt; by Anthony O'Neil, CC BY-SA 2.0; &lt;a href="https://commons.wikimedia.org/wiki/File:Surface_Parking_Lot,_Downtown_Minneapolis_%28433836865%29.jpg" rel="noopener noreferrer"&gt;parking lot&lt;/a&gt; by Tony Webster, CC BY 2.0. The cover image adapts the marina photo with detection boxes and labels and is shared under &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>computervision</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
