树莓派5 — 官方Raspberry Pi OS — OpenCV图像处理 — 2

前言

OpenCV图像处理 — 1

闲来无事又把现在学到的分享出来。

如果帮助到你我很荣幸,如果你有更好的方法和建议可以私信,乐于接受,愿意交流。

说明

环境:树莓派5   Raspberry Pi OS   OpenCV   Python   CSI500万摄像头   STM32F1

内容:树莓派OpenCV简单项目方案   STM32F1串口通信方法   

QR-code二维码   字符提取OCR   网格循迹对齐

OpenCV_Projects

以下应用场景结合 上一篇《OpenCV图像处理 — 1》更佳

Tesseract-OCR引擎

Python提供了支持Tesseract-OCR引擎的Python版本的库pytesseract,pytesseract是一款用于光学字符识别的python工具,即从图片中识别出和读取其中的文字。

pytesseract是该引擎的一种封装,也可以单独作为对Tesseract引擎的调用脚本,支持使用PIL库(Python Image Library)读取各种图片文件类型,包括jpeg,png,gif,bmp等。作为脚本使用时,pytesseract将打印识别出的文字。

树莓派5安装

sudo apt-get update
sudo apt-get install libleptonica-dev
sudo apt-get install tesseract-ocr
sudo apt-get install libtesseract-dev
pip3 install pytesseract
pip3 install pillow

Python只是提供了调用Tesseract的接口,方便我们在Python程序中使用Tesseract,实际运行在Tesseract中。

函数image_to_string

pytesseract.image_to_string(Cropped, config='--psm 11')

参考文章:pytesseract库中的image_to_string函数各参数解释_pytesseract config-CSDN博客

应用实例-车牌识别

环境JupterLab代码块

不同python代码块代表jupyterlab代码块

import cv2
import imutils
import numpy as np
import pytesseract
img = cv2.imread("cd../CAR_License.webp",cv2.IMREAD_COLOR)

路径选择自己的图片路径,放一张可以拿来用的图片

9c881743f9444a64930a788d4a1448a9.png

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 
gray = cv2.bilateralFilter(gray, 5, 15, 15)

cv2.bilateralFilter 图像滤波(双边滤波

一些滤波方法会对图像造成模糊,使得边缘信息变弱。

因此需要一种能够对图像边缘信息进行保留的滤波算法,双边滤波是综合考虑空间信息和色彩信息的滤波方式,在滤波过程中能够有效地保护图像内的边缘信息。

函数使用在此不赘述。

edged = cv2.Canny(gray,30, 200)
contours = cv2.findContours(edged.copy(),cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
contours = sorted(contours, key = cv2.contourArea,reverse=True)[:10]
screenCnt = None

放一张笔记照片吧,不想改格式了:

53a282834fdf44b2b557797d9355df58.png

# 载入显示库
def bgr8_to_jpeg(value, quality=75):
    return bytes(cv2.imencode('.jpg',value)[1])
    
import ipywidgets.widgets as widgets
from IPython.display import display
imagecar = widgets.Image(format='jpeg', width=600, height=400)
imageCropped = widgets.Image(format='jpeg', width=600, height=400)
display(imagecar)
display(imageCropped)
for c in contours: 
    peri = cv2.arcLength(c,-True)
    approx = cv2.approxPolyDP(c, 0.018 * peri, True)
    
    if len(approx) == 4:
        screenCnt=approx
        break
        
if screenCnt is None:
    detected = 0
    print ("No contour detected")
else:
    detected = 1
    
if detected == 1:
    cv2.drawContours(img, (screenCnt), -1, (0, 0, 255), 3)
    
mask = np.zeros(gray.shape,np.uint8)
new_image = cv2.drawContours(mask,[screenCnt],0,255,-1,)
new_image = cv2.bitwise_and(img,img,mask=mask)

(x, y) = np.where(mask == 255)
(topx, topy) = (np.min(x),np.min(y))
(bottomx, bottomy) = (np.max(x),np.max(y))
Cropped = gray[topx:bottomx+1,topy:bottomy+1]

text = pytesseract.image_to_string(Cropped, config='--psm 11')
print("programming_fever's License Plate Recognition\n")
print("Detected license plate Number is:",text)
img = cv2.resize(img,(500,300))
Cropped = cv2.resize(Cropped,(400,200))
# 显示图像
imagecar.value = bgr8_to_jpeg(img)
imageCropped.value = bgr8_to_jpeg(Cropped)
  • 对提取到的轮廓进行处理,找到包含4个顶点的轮廓,表示可能是车牌的轮廓。

  • 如果没有检测到符合条件的轮廓,则打印"No contour detected"。

  • 如果检测到符合条件的轮廓,则在原图像上绘制出车牌的轮廓。

  • 创建一个掩模(mask)来提取车牌区域,并将车牌区域提取出来。

  • 使用Tesseract OCR库识别车牌区域中的文字。

  • 打印出识别到的车牌号码。

339e654dc3054ee89d238d570a59f0bf.jpeg

bb2f803fc1864a4d9c36dbc906505c38.jpeg

QR-code二维码检测

安装Pyzbar

sudo pip3 install pyzbar

应用实例-二维码识别

# 载入必要的库
import cv2
import numpy as np
from pyzbar import pyzbar
import imutils
import sys
import time

# 摄像头初始化
import libcamera 
from picamera2 import Picamera2

picamera = Picamera2()
config = picamera.create_preview_configuration(main={"format": "RGB888", "size": (320,240)},
                                               raw={"format": "SRGGB12", "size": (1920, 1060)})
config["transform"] = libcamera.Transform(hflip=0,vflip=1)
picamera.configure(config)
picamera.start()

# 实时显示图像 没加q键退出
def Video_display(): 
    while True:
        frame1 = picamera.capture_array()
        frame = imutils.resize(frame1,width = 400)
        barcodes = pyzbar.decode(frame)
        for barcode in barcodes:
            (x, y, w, h) = barcode.rect
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
            barcodeData = barcode.data.decode("utf-8")
            barcodeType = barcode.type
            text="{}({})".format(barcodeData, barcodeType)
            cv2.putText(frame, text, (x, y + 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
            print(text)
        
        QR_img.value = bgr8_to_jpeg(frame)

Video_display()

二维码生成

草料二维码生成器 (cli.im)

网格地图视觉对齐

功能背景:在网格地图(白底黑格)中利用视觉去对齐,发送偏移标志给电机控制芯片,进行视觉上的对齐。

# OpenCV
import time
import numpy as np
import cv2
import imutils
# QR-code Tesseract
from pyzbar import pyzbar
import pytesseract
# Angle
import math
# UART
import serial
# Camera
import libcamera
from picamera2 import Picamera2

# 线程相关
import threading
import ctypes
import inspect

# 线程结束代码
def _async_raise(tid, exctype):
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)

def bgr8_to_jpeg(value, quality=75):
    return bytes(cv2.imencode('.jpg', value)[1])

ser = serial.Serial(port = "/dev/ttyAMA0", baudrate = 115200)

# 摄像头初始化
def Camera_Init():
    global picamera
    picamera = Picamera2()
    config = picamera.create_preview_configuration(main={"format": "RGB888"})
    config["transform"] = libcamera.Transform(hflip=0, vflip=1)
    picamera.configure(config)
    picamera.start()

# 摄像头预览 q键退出
def Video_Demo():
    while True:
        video_demo = picamera.capture_array()
        # cv2.line(video_demo, (0, 200), (640, 200), (255, 0, 0), 3)
        cv2.imshow("preview", video_demo)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

# 二维码识别 框选二维码 显示二维码信息 print二维码信息 q键退出
def Video_QRCODE():
    while True:
        frame = picamera.capture_array()
        barcodes = pyzbar.decode(frame)
        for barcode in barcodes:
            (x, y, w, h) = barcode.rect
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
            barcodeData = barcode.data.decode("utf-8")
            barcodeType = barcode.type
            text = "{}({})".format(barcodeData, barcodeType)  # 图像format格式:QRcode信息(类型)
            cv2.putText(frame, text, (x, y + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)  # 在长方形指定位置显示text
            # print(text)
            ser.write(text)
        cv2.imshow("QR-code", frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

# 仓库A1识别
def storehouse_detect():
    while True:
        image = picamera.capture_array()
        cv2.bilateralFilter(image, 13, 15, 15)
        cv2.imshow("storehouse", image)
        if cv2.waitKey(1) & 0xFF == ord('a'):
            text = pytesseract.image_to_string(image)
            print(text)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

def UART_With_STM32():
    global data
    data = [1,2]# need to use it !!
    while True:
        data = ser.read(2)
        data = data[0:].decode()

def Follow_Lines():
    global data
    while True:
        frame = picamera.capture_array()
        frame = frame[240:480,0:640]
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        ret, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
        dst = cv2.bitwise_not(dst)
        cv2.imshow("dst",dst)
        lines = cv2.HoughLinesP(dst, 1, math.pi / 180, 100, minLineLength=400, maxLineGap=5)
        if lines is not None:
            for line in lines:
                x1, y1, x2, y2 = line[0]
            # Calculate angle between line and horizontal line
            angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
            if(abs(angle) < 45):
                cv2.putText(frame, f'Angle: {angle}', (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
                if (data == "ok"):
                    data = [1,2]
                    if angle > 0.5:
                        ser.write(b'2')
                        print("2")
                    elif angle < -0.5:
                        ser.write(b'1')
                        print("1")
                    else:
                        ser.write(b'0')
                        print("0")
        cv2.imshow('Frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    cv2.destroyAllWindows()

if __name__ == '__main__':
    Camera_Init()
    uart = threading.Thread(target=UART_With_STM32)
    follow= threading.Thread(target = Follow_Lines)
    uart.start()
    follow.start()

多线程应用

串口函数需要一直运行,因树莓派5CPU为多核完全可以多线程或多进程,于是就没有选择使用RPi下的中断处理函数了,具体我也不了解树莓派的中断是如何使用的。由于Python的mutiprocessing和threading库太好用了,于是串口等待消息的函数就选择新建线程进行等待操作。

在此简要说明threading多线程操作库的使用方法:

  • 导入线程模块,threading

  • 通过线程类创建线程对象:线程对象 = threading.Thread(target = 任务名/函数名)

  • 启动线程执行任务,线程对象.start()

uart = threading.Thread(target=UART_With_STM32)
follow= threading.Thread(target = Follow_Lines)
uart.start()
follow.start()

主线程会等待所有的子线程执行结束后再结束

如果需要守护主线程(若主线程结束,不等待子线程,子线程全部直接结束),有两种方法

  • 创建子线程时新加参数daemon,令其为True:

    threading_object = threading.Thread(target = f_name, daemon = True)

  • 与多进程中守护主进程方法类似,在开启子线程之前:

    threading_object.setDaemon(True)

与STM32串口通信

对于树莓派的硬件串口的使用

树莓派有2个串口,分别是硬件串口(/dev/ttyAMA0)和mini串口

  • 硬件串口有单独的波特率时钟源,推荐使用。

  • mini串口性能差,功能简单,并且没有波特率专用的时钟源而是由CPU内核时钟提供,一般不使用。

对于树莓派5的硬件串口的使用,可以参考:初学树莓派——(五)树莓派串口收发(硬件串口)-CSDN博客

树莓派5与4B在硬件串口上基本相同,附树莓派5GPIO引脚:

b13d4ca889fe4ce2abea273ac0c9094c.png

注意:

  1. 安装后minicom运行:终端输入minicom -D /dev/ttyAMA0 -b 115200打开; 退出minicom:ctrl+A >> Z >> X

  2. minicom是树莓派的串口助手,就像每一个嵌入式 / 硬件方面的工程师或学生Windows电脑中的串口助手,只不过minicom没有那么多丰富的功能,它只是一个可视化界面,既可以发送数据也可以接收数据。但要注意使用minicom时,窗口显示的是接收到的数据,发送的数据是不会显示在minicom上的。 例如,将树莓派的硬件串口通过USB转串口模块连接个人PC电脑后,树莓派端在minicom输入的内容直接可以在电脑端的串口助手显示,而树莓派端的minicom显示的则是电脑端发送的数据!

  3. minicom在运行时,会抢占串口通道,导致程序代码的堵塞。进行代码上的串口操作时一定要关闭minicom。

  4. UART_With_STM32()函数:data不需要手动清除,可以把它理解成一次性的,data不会重复被操作:例如,STM32每10s向树莓派发送数据“Hello”,而data变量在树莓派的某个程序中每秒被调用判断10次,树莓派在两次接收data数据间,只会进入一次data判断的程序,不会进入100次!

  5. 由于python与C不同,global data之后需要先使用一下data,代表在内存中开辟了这样的一个空间;如果没有data = [1,2]这一行,程序在接收到串口数据之后会报错!我这里是因为STM32会给树莓派传“ok”,于是data = [1,2],具体根据自己的项目要求。

# UART
import serial

ser = serial.Serial(port = "/dev/ttyAMA0", baudrate = 115200) # 端口/dev/ttyAMA0,波特率115200

ser.write(b'Hello World') # 发送数据

# 接收数据函数 一直等待接收
def UART_With_STM32():
    global data  # 以便此变量可以在其他函数中使用,于是将其定义为全局变量。
    data = [1,2] # 重要
    while True:
        data = ser.read(2)
        data = data[0:].decode() # 将data列表从头到尾解码

霍夫变换计算角度

cv2.HoughLines()函数是在二值图像中查找直线段。

函数参数:

HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)

  • image: 必须是二值化图像。推荐使用Canny边缘检测图像。

  • rho:线段以?像素为单位的距离精度,?推荐1即可

  • theta: 角度精度,推荐pi/180,pi为圆周率,math库下。

  • threshod:超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。推荐100,可调。

  • lines:我也不知道什么用:)

  • minLineLength:线段以像素为单位的最小长度,根据应用场景设置,可以多多尝试。

  • maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔,超过设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段。根据项目要求吧,比如计算角度的霍夫直线操作,就可以把它设小点。

此代码功能:定性的传达偏左/右 or 对齐 的信息,定量的没做,因为小车用了PID再加上时间紧迫的原因,没有定量处理。

# 网格视觉对齐 Q键退出
def Follow_Lines():
    global data
    while True:
        frame = picamera.capture_array()
        # frame = frame[240:480,0:640] 选取画面的指定区域进行操作,以免画面的顶部的一些画面影响直线寻找
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 灰度图
        ret, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # 大津法二值化
        dst = cv2.bitwise_not(dst) # 颜色反转 以便霍夫直线检测
        # cv2.imshow("dst",dst) 显示原图
        lines = cv2.HoughLinesP(dst, 1, math.pi / 180, 100, minLineLength=400, maxLineGap=5)
        # 别忘了导入math库,
        if lines is not None:
            for line in lines:
                x1, y1, x2, y2 = line[0]
            # 利用斜率计算角度
            angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
            if(abs(angle) < 45): # 只招对于画面而言|斜率| < 45°的直线。
                # 显示角度并画线
                cv2.putText(frame, f'Angle: {angle}', (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                cv2.line(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
                if (data == "ok"): # 此if下的print是为了方便调试,可以知道视觉辅助对齐时,此时车是否对正了。
                    data = [1,2]
                    if angle > 0.5:
                        ser.write(b'2')
                        print("2")
                    elif angle < -0.5:
                        ser.write(b'1')
                        print("1")
                    else:
                        ser.write(b'0')
                        print("0")
        cv2.imshow('Frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    cv2.destroyAllWindows()

 结语

此文写的比较急,有些程序代码的效果图没放进去,其实是我当初学的时候拍的照片不小心删除找不到了,想学的可以自行尝试。此文也是个人分享。

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值