<?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: Jeremy</title>
    <description>The latest articles on DEV Community by Jeremy (@jp5282).</description>
    <link>https://dev.to/jp5282</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2895788%2F7d926c0b-335b-4cca-916e-fdfd6104d76c.gif</url>
      <title>DEV Community: Jeremy</title>
      <link>https://dev.to/jp5282</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jp5282"/>
    <language>en</language>
    <item>
      <title>Read license plate with OCR</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Sat, 08 Mar 2025 14:55:30 +0000</pubDate>
      <link>https://dev.to/jp5282/read-license-plate-with-ocr-59hj</link>
      <guid>https://dev.to/jp5282/read-license-plate-with-ocr-59hj</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;So close! We now have cropped images with just license plate numbers. This is derived from highway-view, down to car-view, down to license plate. In this step, we pre-process the image to improve OCR, and we read the characters from image.&lt;/p&gt;

&lt;h2&gt;
  
  
  ToC (Step-by-Step)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;p&gt;The following pre-processing steps helps focus OCR on just the important image information, and reduces everything else (eg RBG color, grays, small contours). For clarity this sequence of pre-processing prior to OCR is fairly typical pattern prior to OCR taught in computer vision. I’m not inventing or discovering something new here. At the bottom of article, we run OCR with no pre-processing to illustrate the difference. &lt;/p&gt;

&lt;p&gt;Before pre-processing - cropped image of license plate from prior steps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlniw55jcdkygnkvih3t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlniw55jcdkygnkvih3t.jpg" alt="Cropped image for OCR" width="195" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Pre-process step: desaturate colors. Go gray.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xn83mc1ydjjzmehplpb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xn83mc1ydjjzmehplpb.jpg" alt="Desaturated input for OCR" width="195" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RGB color is not helpful here for OCR. Doesn’t matter if license plate numbers are magenta or cyan. Thus discard color information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pre-process step: gaussian blur to reduce noise / artifacts slightly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yw3k5zxq735wepvjfzr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yw3k5zxq735wepvjfzr.jpg" alt="Blurred input for OCR" width="195" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nevermind the “gaussian” part. This is just applying a blur filter to the image. That blur filter softens the small differences. And because small differences are reduced (eg dirty-ish license plate), then the big differences are easier to distinguish (eg dark-numbers on white-background).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pre-process step: threshold to really highlight license plate characters&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjsw7vwwzdpsuser5p6b6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjsw7vwwzdpsuser5p6b6.jpg" alt="Thresholded input for OCR" width="195" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thresholding further highlights where we have big differences such as dark-numbers on white-background. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OCR license plate&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhimmsmfia64ohv9a64s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhimmsmfia64ohv9a64s.jpg" alt="OCR without pre-processing" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I experimented with a bunch of OCR libraries here. I found best for this use case was PaddleOCR, better than Tesseract and some others. Run PaddleOCR on thresholded image, and get string text, license plate number, back from the image!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Profit!&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;paddleocr&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PaddleOCR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draw_ocr&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ppocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_logger&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;

&lt;span class="cp"&gt;# Initialize PaddleOCR
&lt;/span&gt;&lt;span class="n"&gt;ocr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaddleOCR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_angle_cls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_space_char&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;avoid&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt; &lt;span class="n"&gt;statements&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;OCR&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;

&lt;span class="cp"&gt;#baseDir = '/Users/japollock/Projects/TrainHighwayCarDetector/'
&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;img_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;croppedPlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;IMG_4554_0002&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Human readable is CEZ2594"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_RGB2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;blur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GaussianBlur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&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;ret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blur&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="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;THRESH_OTSU&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;THRESH_BINARY_INV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cp"&gt;# Perform OCR
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thresh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;True&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;line&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Print statement outputs&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[[[[14.0, 29.0], [181.0, 22.0], [184.0, 76.0], [17.0, 83.0]], ('CEZ2594', 0.9654235243797302)]]&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;We read the license plate! Number to the right of string is confidence ie ~96.5% confident string is correct. And the left side numbers is bounding box coordinates. 🥳&lt;/p&gt;

&lt;p&gt;Next Link: Tie it all together: End-to-end License Plate Detection&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://paddlepaddle.github.io/PaddleOCR/main/en/index.html#pp-ocrv3-chinese-model" rel="noopener noreferrer"&gt;https://paddlepaddle.github.io/PaddleOCR/main/en/index.html#pp-ocrv3-chinese-model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/paddleocr/2.4/" rel="noopener noreferrer"&gt;https://pypi.org/project/paddleocr/2.4/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix: Interesting Points
&lt;/h2&gt;

&lt;p&gt;Folks might wonder: why not just OCR the original image? You can. But this is what you get:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk0fgsraffeu9bbzmae2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk0fgsraffeu9bbzmae2.jpg" alt="OCR without pre-processing" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the right hand side not the red bounding box. Firstly, three of seven characters are wrong. This is the same input we just demonstrated, same license plate same image, only without pre-processing steps. Secondly, confidence score is low. This is why the pre-processing steps.&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/url"&gt;TBD -- End-to-end real-time detection of license plates&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Crop Bounding Box ( part deux! ) for License Plate</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Tue, 04 Mar 2025 13:28:45 +0000</pubDate>
      <link>https://dev.to/jp5282/crop-bounding-box-part-deux-for-license-plate-i28</link>
      <guid>https://dev.to/jp5282/crop-bounding-box-part-deux-for-license-plate-i28</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;We are pre-processing photos to ultimately enable OCR. Prior step we found the bounding box for license plates. Let’s crop down images to only license plates themselves for OCR next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;p&gt;The over-arching goal is to read license plate numbers from images. On that journey, we detected cars in highway image captures, cropped to just cars, and detected license plates on car images. Now we crop to just license plates. And those zoomed-in license plate images are inputs to OCR, which will read license plate numbers into database.&lt;/p&gt;

&lt;p&gt;Unlike prior cropping step, we can safely assume one license plate per image. It’s not 1:[0,n) it’s 1:[0,1].&lt;/p&gt;

&lt;p&gt;Thus, simple step: if we found a license plate (ie have a bounding box), then crop license plate&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt; 
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;japollock&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;inputFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;IMG_4554_0002&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;inputFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;outputPhotosDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;croppedPlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;detect&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolov8n_100_16_LP_v27&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;imageOriginal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;imageScaled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageOriginal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imageOriginal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;imageScaled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;imageScaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imgsz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&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;results&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;results&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;boxes&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;boxes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&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;boxes&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="cp"&gt;# bounding box in scaled-down image
&lt;/span&gt;    &lt;span class="n"&gt;x1Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;x2Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;y1Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;y2Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="cp"&gt;# calc bounding box in original (scaled-up) image
&lt;/span&gt;    &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x1Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y1Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;x2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x2Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y2Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="cp"&gt;# cropped
&lt;/span&gt;    &lt;span class="n"&gt;imageCropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imageOriginal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;outputFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outputPhotosDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imageCropped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Wrote&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;outputFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we’re enabling from highway to car to license plate, such as the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnnuxrqbb3q5z6w2dq4j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwnnuxrqbb3q5z6w2dq4j.jpg" alt="Highway view" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzltdywt7wjuuckv96nu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzltdywt7wjuuckv96nu.png" alt="Cropped car, and cropped license plate" width="791" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost there! From highway-view image capture, to car, to license plate. Next up: OCR!&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/read-license-plate-with-ocr-59hj"&gt;Read license plate with OCR&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Model for Detecting License Plates in Cropped Image</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Sat, 01 Mar 2025 18:54:28 +0000</pubDate>
      <link>https://dev.to/jp5282/model-for-detecting-license-plates-in-cropped-image-2kg1</link>
      <guid>https://dev.to/jp5282/model-for-detecting-license-plates-in-cropped-image-2kg1</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;Now that we have per-car images, we need to find license plates. License plates might not be present, or might be otherwise difficult to find with heuristic, thus we use YOLO again to find the license plate bounding box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;p&gt;We now have cropped images per car. Our high-level goal is OCR on the license plate. OCR needs a tight view to only the license plate characters, and not other distractions. This means within our cropped images, we should find license plate (this article), and crop image to just license plate (next).&lt;/p&gt;

&lt;p&gt;We tested heuristics to find license plates such as contour-detection, and they did not generalize well. See image examples below – illustrating that even with reliable car detection, license plates can be easy (centered, large), harder (off-center, smaller), or even not-present. YOLO proved to be a better tool for variation, and thus we train second model for license plates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypettj3lpwq01x7k59bl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypettj3lpwq01x7k59bl.jpg" alt="Easy example" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Easy-to-see license plate. Centered, large. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhz4js7nhgnqqiuj89gm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhz4js7nhgnqqiuj89gm.jpg" alt="Less-easy example" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also easy, but less-easy. License plate is no longer centered on car. Can’t find license plate based on location in cropped image. And license plate is smaller in this view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77sno7st3ffoxp8lvx59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77sno7st3ffoxp8lvx59.png" alt="Harder example" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Harder example. This is a real output from step 3 model for detecting cars. Just not-yet in-frame. Contour heuristic thinks this is a license plate, but we need model capable of understanding its incomplete, and to move on.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Collect training data&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run step 1 through 4 for a bit to collect training data. In my case, I compiled ~30 cropped images of cars with visible license plates. Example such as the following:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhz4js7nhgnqqiuj89gm.jpg" alt="Example cropped car image" width="800" height="478"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Label training data&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is time-consuming and manual step, but important.&lt;/li&gt;
&lt;li&gt;I used roboflow.com (same as step 3). I recommend their tooling. I’m sure there’s alternatives if you prefer, but roboflow made e2e process easier. Training the YOLO model requires label data in a particular format, which roboflow automates, and roboflow has online tooling to streamline your labeling data.&lt;/li&gt;
&lt;li&gt;Upload your images&lt;/li&gt;
&lt;li&gt;Hand-label your images. I suggest learning the hotkeys.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmsxagexrm3ofx387a1c.png" alt="Sample labeling from roboflow" width="800" height="559"&gt;
Sample labeling from roboflow - one of many&lt;/li&gt;
&lt;li&gt;I suggest defining guidelines for yourself to mitigate the monotony. My bounding boxes for labeling generally stretched within the license plate - not encircling the plate border itself. Otherwise top-left to bottom-right within the plate rectangle, and repeat. I’m not saying use the same bounding box. I’m suggesting: know what your guideposts are. Knowing your guideposts will make your labeling consistent and help the tedium.&lt;/li&gt;
&lt;li&gt;Download training data. This is just the formatted xml capturing all your bounding boxes for training.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Train model YOLO&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note that my output model is “v27” ie I trained several models to experiment with different parameters. I recommend testing and iterating on configurations that work for you: nano model vs medium, less epochs or more, etc.&lt;/li&gt;
&lt;li&gt;On MacBook M1 Pro with 32GB memory, took &amp;lt;1 hour to train the winning model.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;

&lt;span class="cp"&gt;###########
# Training
# Load the model
&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;japollock&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolov8n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;imgsz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="cp"&gt;#   epochs=100,
#   batch=16,
#   device='mps',
&lt;/span&gt;   &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v4data_LP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="n"&gt;yolov8n_100_16_LP_v27&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;###########
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test YOLO.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I trained five model variants on model and epoch variations. nano, medium, xlarge models, and 50 or 100 epochs. I measured performance on precision, recall, map50, and map50-95. Long story short, all variants had near-equal very good performance. Thus I choose simplest model nano with default epochs
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt; 
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;

&lt;span class="cp"&gt;###########
# predict
&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;japollock&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;

&lt;span class="cp"&gt;###########
# model
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;detect&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolov8n_100_16_LP_v27&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cp"&gt;###########
# images
&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;IMG_4566_0002&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;imgsz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&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;plot&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitKey&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;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni41fce1brx030i8ylzj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni41fce1brx030i8ylzj.jpg" alt="Sample classification from best license plate model" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
Sample classification from best model. “license-plates” is the classifier label.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we have model detecting license plates inside the cropped image for cars. This enables next step, which is to crop down image to just license plate characters, on the way to OCR after.&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/crop-bounding-box-part-deux-for-license-plate-i28"&gt;Crop bounding box for license plate&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Crop Bounding Box + Rotate</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Thu, 27 Feb 2025 13:28:41 +0000</pubDate>
      <link>https://dev.to/jp5282/crop-bounding-box-rotate-4757</link>
      <guid>https://dev.to/jp5282/crop-bounding-box-rotate-4757</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;We are pre-processing photos to ultimately enable OCR. We have bounding boxes for rear-end of cars; now let’s rectangularize the license plates with cropped and rotated photos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;p&gt;We now have model identifying cars in our streamed images from camera. Our high-level goal is cropped-image reduced only to license plate for OCR reading. That means finding cars in image (article 3), cropping image to just rear bumper of car (this article; 4), so that we can find license plate (next article; 5), and crop image to just license plate (article 6).&lt;/p&gt;

&lt;p&gt;This interior step is a processing step to ultimately enable OCR. Note particularly in my captured images that images are both looking down, but also from the distant side, from the cars. That eschew is important feature to reduce. OCR expects a straight-on view, thus part of this step is also corrected eschew (by rotation).&lt;/p&gt;

&lt;p&gt;At this point we have bounding box per car. We potentially have multiple cars per image - so within this step is a loop that forks all the following steps (for each car: detect license plate, crop, OCR, repeat).&lt;/p&gt;

&lt;p&gt;First, we are cropping image to just bounding box from model in step 3, and rotating to match camera’s eschewed perspective in relation to highway.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;For each bounding box (ie for each car)&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4lgso9esbob5d0vygob.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4lgso9esbob5d0vygob.JPG" alt="Sample highway capture" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Input image. Run model, find bounding boxes (cars).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Crop to rear bumper, and rotate to correct eschew&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf7vslcl6ts33vem2r3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf7vslcl6ts33vem2r3s.png" alt="2x extracted cropped images of cars" width="768" height="227"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Output images cropped down to just rear bumper with clearer view of license plate. Note rotation artifacts in corners. Now, license is straight-on for easier OCR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt; 
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;japollock&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;inputFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_cars&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;IMG_4553&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;inputFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;outputPhotosDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_cars&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;licensePlates&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;detect&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolov8m_v21_100e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;imageOriginal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;imageScaled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageOriginal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imageOriginal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;imageScaled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;imageScaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imgsz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&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;boxes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="cp"&gt;# bounding box in scaled-down image
&lt;/span&gt;    &lt;span class="n"&gt;x1Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;x2Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;y1Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;y2Float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyxy&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="cp"&gt;# calc bounding box in original (scaled-up) image
&lt;/span&gt;    &lt;span class="n"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x1Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y1Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;x2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x2Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y2Float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="cp"&gt;# cropped
&lt;/span&gt;    &lt;span class="n"&gt;imageCropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imageOriginal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="cp"&gt;# rotated
&lt;/span&gt;    &lt;span class="n"&gt;image_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tuple&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="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageCropped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;/&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;rot_mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRotationMatrix2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;imageRotated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warpAffine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageCropped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rot_mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imageCropped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INTER_LINEAR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;outputFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outputPhotosDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sc"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mo"&gt;04&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imageRotated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Wrote&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;outputFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have image clearly showing license plate. We still need to enhance-enhance before handing off to OCR. Thus repeat as before: find the license plate in the now-cropped image, in order to subsequently crop to just license plate. Next: YOLO model for license plates!&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/model-for-detecting-license-plates-in-cropped-image-2kg1"&gt;Model for detecting license plates in cropped image&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: Interesting Points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rotation (2d), and not perspective warping (3d) was an evolution of process. Initially I thought I would use perspective warping via homography as I knew how. But long story short, I realized it was over-engineering after tinkering on the solution. Why go through the effort of calculating perspective warp from my camera to highway, when a simple rotation of the image would work just as well - and a lot more simple?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Model for Detecting Cars</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Tue, 25 Feb 2025 11:34:50 +0000</pubDate>
      <link>https://dev.to/jp5282/model-for-detecting-cars-license-plate-detection-3i74</link>
      <guid>https://dev.to/jp5282/model-for-detecting-cars-license-plate-detection-3i74</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;Build YOLO model from scratch to find bounding box of cars in images. This includes labeling test images, training model, and evaluating different settings for best model output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;p&gt;At this point we have focused camera, automatically capturing and transferring images, and we’re ready to process photos on the way to reading license plate numbers.&lt;/p&gt;

&lt;p&gt;As humans, we see cars in the images from prior article. But our computers don't distinguish those entities yet like we can - not without being trained to. Thus, we will teach the computer what a car on a highway looks like.&lt;/p&gt;

&lt;p&gt;Enabling computer to know what a car looks like distills essentially to: 1) collect training data, 2) label training data, 3) train model on training data, 4) train models with varying configurations and choose best. We’ll walk through those steps next.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Collect training data&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use for loop’ing capture from step two under good light for a bit. I compiled 145 images.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiu8j56egnz6tbge12qap.jpg" alt="Sample image from my camera setup for labeling" width="800" height="533"&gt;
Sample image&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Label training data&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I used roboflow.com. I recommend their tooling. I’m sure there’s alternatives if you prefer, but roboflow made e2e process easier. Training the model requires label data in a particular format, which roboflow automates for you, and roboflow has online tooling to streamline your building of the labeling data.&lt;/li&gt;
&lt;li&gt;Upload your images&lt;/li&gt;
&lt;li&gt;Hand-label your images. This is time consuming. I suggest learning the hotkeys.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62ozj2hwt2w9dd5y5plr.png" alt="Sample labeling of cars on highway" width="800" height="532"&gt;
Sample labeling from roboflow - one of many&lt;/li&gt;
&lt;li&gt;I suggest defining guidelines for yourself to mitigate the monotony. My bounding boxes for labeling generally stretched from top-left turn-signal light to bottom-right wheel-well highlighting the entire rear bumper. I’m not saying use the same bounding box. I’m suggesting: know what your guideposts are. Knowing your guideposts will make your labeling consistent and help the tedium.&lt;/li&gt;
&lt;li&gt;Download training data. This is just the formatted xml capturing all your bounding boxes for training.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Train model YOLO&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Note that my output model is “v21” ie I trained several models to experiment with different parameters. I recommend testing and iterating on configurations that work for you: nano model vs medium, less epochs or more, etc.&lt;/li&gt;
&lt;li&gt;I found medium with default epoch and batch settings produced best results for my training set + labels. I trained five variant models including nano, medium, xlarge, and various permutations of epoch and batch.&lt;/li&gt;
&lt;li&gt;On MacBook M1 Pro with 32GB memory, took 4.5 hours to train the winning model. Five permutations of training different configurations took ~33 hours total.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;

&lt;span class="cp"&gt;###########
# Training
# Load the model
&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;japollock&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolov8m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;imgsz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="cp"&gt;#   epochs=100,
#   batch=8,
#   device='mps',
&lt;/span&gt;   &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v2data_CARS&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;yolov8m_100_8_CARS_v21&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="cp"&gt;###########
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test YOLO.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I compared five model variants on ClearML score dimensions: precision, recall, map50, and map50-95. I confirmed model performance manually visualizing the results with above script. Did this for both best and worst model. Worst model had false-positives, multiple detections on single car, and generally lower confidence score. Best model had no false-positives (in limited testing), no multiple detections, etc. Helped to see the output in addition to ClearML scoring.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt; 
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;imutils&lt;/span&gt;

&lt;span class="cp"&gt;###########
# predict
&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;japollock&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TrainHighwayCarDetector&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;

&lt;span class="cp"&gt;###########
# model
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;detect&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolov8m_v21_100e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cp"&gt;###########
# images
&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDir&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yolo_cars&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;IMG_4553&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jpg&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;imgsz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&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;plot&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitKey&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;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmd1yoxjxxqah4v4691sv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmd1yoxjxxqah4v4691sv.jpg" alt="Successfully detecting cars in highway image" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Sample classification from best model. “Cars” is the classifier label.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we have model detecting cars bounding box. This enables us to hone in on license plate from highway view. Next up is cropping down to bounding box, and perspective warp’ing to rectangular-ize the license plate from eschew.&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/crop-bounding-box-rotate-4757"&gt;Crop bounding box + rotate&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://app.roboflow.com/trainhighwaydetection" rel="noopener noreferrer"&gt;https://app.roboflow.com/trainhighwaydetection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnopencv.com/train-yolov8-on-custom-dataset/" rel="noopener noreferrer"&gt;https://learnopencv.com/train-yolov8-on-custom-dataset/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;​​&lt;a href="https://app.clear.ml/projects/259cb1f4acbf49f4be55654b329429b1/experiments/049c87e301e84eb0986d74d7b5ed16d1/output/log" rel="noopener noreferrer"&gt;https://app.clear.ml/projects/259cb1f4acbf49f4be55654b329429b1/experiments/049c87e301e84eb0986d74d7b5ed16d1/output/log&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix: Interesting Points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rabbit hole of car detectors

&lt;ul&gt;
&lt;li&gt;Training a model to detect cars was an evolution in process. I started with the idea that I could just download an off-the-shelf model somewhere on the Internet and use that.&lt;/li&gt;
&lt;li&gt;First contact with “cars” model &lt;a href="https://www.analyticsvidhya.com/blog/2021/12/vehicle-detection-and-counting-system-using-opencv/#wait_approval" rel="noopener noreferrer"&gt;https://www.analyticsvidhya.com/blog/2021/12/vehicle-detection-and-counting-system-using-opencv/#wait_approval&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;BUT from what I can gather first link was really based on this work &lt;a href="https://github.com/andrewssobral/vehicle_detection_haarcascades" rel="noopener noreferrer"&gt;https://github.com/andrewssobral/vehicle_detection_haarcascades&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Actual academic style paper detailing classifier &lt;a href="https://github.com/andrewssobral/vehicle_detection_haarcascades/blob/master/doc/Automatic_Detection_of_Cars_in_Real_Roads_using_Haar-like_Features.pdf" rel="noopener noreferrer"&gt;https://github.com/andrewssobral/vehicle_detection_haarcascades/blob/master/doc/Automatic_Detection_of_Cars_in_Real_Roads_using_Haar-like_Features.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Aside from folks using the model without attribution, this model has terrible performance on my camera setup (ie eschewed orientation to cars; only rear-facing), and probably will on yours too. Models generally will have this quality - they will work well on their training data, and not necessarily yours. I’m sure this model was great for detect front-orientation of a car, or side-view. But our setup necessitated training a model from scratch.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Capturing Images from DSLR to RPi</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Mon, 24 Feb 2025 13:42:46 +0000</pubDate>
      <link>https://dev.to/jp5282/capturing-images-from-dslr-to-rpi-license-plate-detection-31aj</link>
      <guid>https://dev.to/jp5282/capturing-images-from-dslr-to-rpi-license-plate-detection-31aj</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;Now that camera is capturing (manually) photos of high-speed cars - we need to trigger image capture and transfer to RPi automatically. This enables streaming of images from DSLR to RPi, and then makes those images available for post-processing in subsequent steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;p&gt;At this point we should have camera mounted, connected to RPi, and capturing nicely focused images (manually) of cars and license plates.&lt;/p&gt;

&lt;p&gt;Now that we have manual capture working, we will write code to control DSLR from RPi. We will capture image, transfer to RPi, and repeat.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Capture image - borrows heavily from &lt;a href="https://thezanshow.com/electronics-tutorials/raspberry-pi/tutorial-41" rel="noopener noreferrer"&gt;https://thezanshow.com/electronics-tutorials/raspberry-pi/tutorial-41&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use gphoto2 library to communicate with DSLR over USB from RPi. The following code uses basic “--capture-image-and-download” command to capture, transfer, and repeat.&lt;/li&gt;
&lt;li&gt;The kill gphoto2 pid is an unfortunate RPi thing, where the OS auto-mounts the DSLR as a drive on USB connection. Thus for gphoto2 to issue capture command, we need to kill pid first. It’s weird, but it is a thing.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gphoto2&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;gp&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="cp"&gt;# kill the gphoto process from turning on the camera or rebooting the RPi
&lt;/span&gt;&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;killGphoto2Process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;communicate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="cp"&gt;# search for the process we want to kill
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;gvfsd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gphoto2&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="cp"&gt;# kill that process!
&lt;/span&gt;            &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;None&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGKILL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;captureImages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;captureFilenameRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CANON&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;JPG&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captureCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;captureFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captureFilenameRegex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captureFilename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"did not regex-extract filename. exiting"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;quit&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;captureFilename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cp"&gt;##########################################################################
# begin main
&lt;/span&gt;&lt;span class="n"&gt;captureCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;saveLocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TakePhoto1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;

&lt;span class="cp"&gt;# actually operate camera
&lt;/span&gt;&lt;span class="n"&gt;killGphoto2Process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saveLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;captureFilename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;captureImages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captureFilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transfer image&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedyfdw36my4ou4y7nslk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedyfdw36my4ou4y7nslk.jpg" alt="Single image captured from DSLR" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Capture one automatically to RPi&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fogpoawe1iw6hbaiu9uqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fogpoawe1iw6hbaiu9uqj.png" alt="Loop images captured from DSLR" width="800" height="421"&gt;&lt;/a&gt;&lt;br&gt;
Capture, transfer, repeat - automatically from DSLR to RPi.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we have DSLR zoomed in on highway, capable of capturing high-speed car with detail, RPi is driving capture and transferring photos to storage. Next up is honing in on license plates per photo by detecting cars in each image.&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/model-for-detecting-cars-license-plate-detection-3i74"&gt;Model for detecting cars&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thezanshow.com/electronics-tutorials/raspberry-pi/tutorial-41" rel="noopener noreferrer"&gt;https://thezanshow.com/electronics-tutorials/raspberry-pi/tutorial-41&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gphoto.org/doc/remote/" rel="noopener noreferrer"&gt;http://www.gphoto.org/doc/remote/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;More on the need for kill gphoto2 pid  &lt;a href="https://forums.raspberrypi.com/viewtopic.php?t=202934" rel="noopener noreferrer"&gt;https://forums.raspberrypi.com/viewtopic.php?t=202934&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix: Interesting Points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Learned through practice that light conditions influence heavily successful capture of images. This rig doesn’t work at night (not yet?) I suspect this is why highway toll cameras are set up in arrays like this - closer to highway surface, and without perspective eschew.&lt;/li&gt;
&lt;li&gt;For anyone concerned, taking photos in public (eg of license plates) are not restricted legally. &lt;a href="https://www.google.com/search?q=photos+in+public" rel="noopener noreferrer"&gt;https://www.google.com/search?q=photos+in+public&lt;/a&gt; and &lt;a href="https://www.acludc.org/en/know-your-rights/if-stopped-photographing-public" rel="noopener noreferrer"&gt;https://www.acludc.org/en/know-your-rights/if-stopped-photographing-public&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>Camera and Computer Setup</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Sun, 23 Feb 2025 22:03:08 +0000</pubDate>
      <link>https://dev.to/jp5282/camera-and-computer-setup-license-plate-detection-2g99</link>
      <guid>https://dev.to/jp5282/camera-and-computer-setup-license-plate-detection-2g99</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;The following article will read like a recipe. We enumerate hardware components (ingredients), describe configuration (recipe), and connect. This is the foundation on which we’ll build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingredients (Hardware)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;EOS 70D DSLR, and AC adapter for continuous power&lt;/li&gt;
&lt;li&gt;300mm AF f/4-5.6 lens for EOS 70D&lt;/li&gt;
&lt;li&gt;Tripod for fixed position capture&lt;/li&gt;
&lt;li&gt;Raspberry Pi 4 Model B, AC adapter, with Raspbian, Python, and OpenCV installed&lt;/li&gt;
&lt;li&gt;USB cable connecting camera to Raspberry Pi, USB-A to USB Mini-B&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recipe
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Setup Raspberry Pi with Raspbian, Python, and OpenCV libraries. Links [&lt;a href="https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up/2" rel="noopener noreferrer"&gt;1&lt;/a&gt;] and [&lt;a href="https://www.zdnet.com/article/raspberry-pi-4-model-b-and-raspbian-buster-how-to-set-up-your-board/" rel="noopener noreferrer"&gt;2&lt;/a&gt;] are good models to follow.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup camera. Mount, power it up. Don’t connect USB cable yet; we can work on USB capture after adjusting camera settings next.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56oc203wwsv1dfs4trsh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56oc203wwsv1dfs4trsh.jpg" alt="My Canon DSLR setup visualized 1" width="800" height="602"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmklb2fsgxs4ajtvh1lnp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmklb2fsgxs4ajtvh1lnp.jpg" alt="My Canon DSLR setup visualized 2" width="800" height="602"&gt;&lt;/a&gt;&lt;br&gt;
Your camera setup will be different; but for folks modeling after mine, here’s how it’s set up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Focus the camera lens on roadway. Use M setting for custom lens settings. I suggest finding a static piece of roadway like lane-line edge to check tack-sharp focus. Use magnify button to 10x on viewfinder to ensure focus.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoxnfm6gsk9109v2ipnf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoxnfm6gsk9109v2ipnf.jpg" alt="Illustrating good focus on 10x zoom on DSLR" width="800" height="1062"&gt;&lt;/a&gt;&lt;br&gt;
This is 10x digital zoom testing lens focus. Picture is of a road reflector in highway asphalt.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fair warning: your configuration here will vary based on light and distance. For me, I am ~110 ft from highway surface (camera looking down on highway from home), and I live in rainy Seattle. My lens choice, 300mm zoom lens, and my settings reflect this.&lt;/li&gt;
&lt;li&gt;To discover your best settings, I suggest first 1) set fast shutter speed, then 2) zoom, then 3) focus, then 4) test and iterate with taking picture and confirming with 10x digital zoom that you have sufficient clarity for human eye to read license plate from camera viewfinder.&lt;/li&gt;
&lt;li&gt;My settings were 1/1000 shutter speed, f/5, and auto-ISO&lt;/li&gt;
&lt;li&gt;I spent probably the most time on this step learning about fstop and ISO, and experimenting. This was key for high-speed detailed image capture.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Capture photo - use viewfinder - demonstrating that you have the right zoom/focus. Use viewfinder to zoom and explore photo, and confirm you can read license plate manually.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwdd5ao0wa7m3sfxsee5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwdd5ao0wa7m3sfxsee5.jpg" alt="Showing we can capture cars even on highway" width="800" height="1062"&gt;&lt;/a&gt;&lt;br&gt;
Demonstrating to myself that I could capture images, and see license plate from viewfinder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect USB cable between camera and Raspberry Pi&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm RPi OpenCV with simple script below. Equivalent of OpenCV “hello world!”&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;# tutorial
# https://docs.opencv.org/4.x/db/deb/tutorial_display_image.html
&lt;/span&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;photosPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/Users/japollock/Projects/TakePhoto1/photos/"&lt;/span&gt;
&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"starry_night.jpg"&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photosPath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;filename&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;img&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not read the image."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Display window"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitKey&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we have working camera, working RPi, working python OpenCV, and ready to build on this foundation to capture images from DSLR with commands sent by RPi.&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/capturing-images-from-dslr-to-rpi-license-plate-detection-31aj"&gt;Capturing images from DSLR to RPi&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up/2" rel="noopener noreferrer"&gt;https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.zdnet.com/article/raspberry-pi-4-model-b-and-raspbian-buster-how-to-set-up-your-board/" rel="noopener noreferrer"&gt;https://www.zdnet.com/article/raspberry-pi-4-model-b-and-raspbian-buster-how-to-set-up-your-board/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.opencv.org/4.x/db/deb/tutorial_display_image.html" rel="noopener noreferrer"&gt;https://docs.opencv.org/4.x/db/deb/tutorial_display_image.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.motorsportphotographer.com/motorsport-photography-camera-settings-cheat-sheet/#:%7E:text=1/2000th%20to%201/1000th,ensure%20the%20sharpest%20possible%20photo" rel="noopener noreferrer"&gt;https://www.motorsportphotographer.com/motorsport-photography-camera-settings-cheat-sheet/#:~:text=1/2000th%20to%201/1000th,ensure%20the%20sharpest%20possible%20photo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix: Interesting Points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DSLR was an evolution in my camera choice, but necessary in the end. I started with a Raspberry Pi HD camera, and could not achieve the zoom and focus required for high-speed car image capture.&lt;/li&gt;
&lt;li&gt;In my case, I am 10 stories up from highway surface, and probably 40 ft laterally removed - this is why the 300mm zoom was necessary and fully utilized. This is related to why DSLR + lens was required instead of RPi HD camera.&lt;/li&gt;
&lt;li&gt;venv (Virtual env) helped a lot - particularly with going back and forth between writing code on mac book, then executing code on RPi. Helped navigate package dependencies, and avoid dependency-hell that might otherwise have gotten in the way.&lt;/li&gt;
&lt;li&gt;pyenv (Python version management) also helped a lot - given that paddleOCR is finicky about which python version works correctly. In my case I used 3.9.21.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
    <item>
      <title>OpenCV in Python for End-to-end License Plate Detection</title>
      <dc:creator>Jeremy</dc:creator>
      <pubDate>Sun, 23 Feb 2025 21:43:02 +0000</pubDate>
      <link>https://dev.to/jp5282/opencv-in-python-on-raspberry-pi-for-license-plate-detection-435o</link>
      <guid>https://dev.to/jp5282/opencv-in-python-on-raspberry-pi-for-license-plate-detection-435o</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;The following outlines a multi-step workflow to capture images of cars driving by on the highway, read license plates, and write those to a local database. Each step in the workflow has a dedicated article and how-to. Feel free to hook in wherever!&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;This article details how I built my own license plate detection rig with the idea that I can help you build something similar. This workflow operates in real time on a raspberry pi 4b connected to a Canon DSLR, with testing and iterating on my MacBook. The camera looks down on the i5 northbound highway in our Seattle condo. I share how the pipeline works end-to-end from capture to detection to ocr and storage. Code is shared and photo/resources for reproducing on your end, as well as outlining challenges I encountered in case you have similar problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uc7ooyle5gjhyxos4rr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uc7ooyle5gjhyxos4rr.png" alt="Labeled highway cars" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9o616pmt4nxjcgdner0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9o616pmt4nxjcgdner0w.png" alt="Labeled license plate" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a weird but fun build, with no intended business application. I thought maybe it would be neat to build a query mechanism for amber alerts - get an alert, then check if/when license was last observed. But just a toy idea. And really I just wanted a hobby project to build after studying computer vision in university – a project that would be fun to enjoy tinkering around. &lt;/p&gt;

&lt;p&gt;As of this writing, the workflow is ~90% complete. Only OCR is not working reliably. Each other step has gone through evolutions from ideation to production. For example, the camera rig originally started as a raspberry pi HQ camera. But iterations later, it was clear that a raspberry pi HQ camera could not capture high-speed cars in detail. Instead, an EOS DSLR with 300mm lens proved better. :) This is a journey! Hopefully my experience can help some other folks interested in similar concepts - and so I detail here what I have accomplished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqyerv4kc57945ampmf3.png" alt="License plate detection workflow visualized" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/opencv-in-python-on-raspberry-pi-for-license-plate-detection-435o"&gt;Overview&lt;/a&gt;: OpenCV in Python for End-to-end License Plate Detection.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/camera-and-computer-setup-license-plate-detection-2g99"&gt;Camera and computer setup&lt;/a&gt;. Raspberry pi (RPi), Canon DSLR, f-stop and ISO.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/capturing-images-from-dslr-to-rpi-license-plate-detection-31aj"&gt;Capturing images from DSLR to RPi&lt;/a&gt;. Automating image capture and transfer to RPi.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/model-for-detecting-cars-license-plate-detection-3i74"&gt;Model for detecting cars&lt;/a&gt;. Train YOLO model from scratch and label images.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/crop-bounding-box-rotate-4757"&gt;Crop bounding box + rotate&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/model-for-detecting-license-plates-in-cropped-image-2kg1"&gt;Model for detecting license plates&lt;/a&gt;. Train a second YOLO model.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/crop-bounding-box-part-deux-for-license-plate-i28"&gt;Crop bounding box&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jp5282/read-license-plate-with-ocr-59hj"&gt;Read license plate with OCR&lt;/a&gt;. Pre-process image, and extract text with Paddle OCR.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/url"&gt;TBD -- End-to-end real-time detection of license plates&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each article is self-contained. If you’re only interested in building and training your own CV model, then you can read just article (3). If you’re interested in how to set up a camera to capture tack-sharp images of highway speed cars, then you can read just article (1). If you want to build a whole system end-to-end, then this series should help. Have fun!&lt;/p&gt;

&lt;p&gt;Next Link: &lt;a href="https://dev.to/jp5282/camera-and-computer-setup-license-plate-detection-2g99"&gt;Camera and computer setup&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>computervision</category>
    </item>
  </channel>
</rss>
