bus 001 Device 002是摄像头 ID 05a3:9230 ARC International
然后输入
dmesg
从上面的打印信息中找到了uvc设备
https://www.cnblogs.com/qixianyu/p/6575276.html
安装usb_camera
sudo apt install ros-melodic-usb-cam
安装好usb_cam包之后,判断设备确定自己的摄像头是viedo0还是viedo1还是...
cd /dev &&find . -name "video*"
读取图像:
roslaunch usb_cam usb_cam-test.launch
这里的launch文件默认是的"/dev/video0",如果你的是其它名称,则需要执行以下命令(这里以"/dev/video1"举例)
roslaunch usb_cam usb_cam-test.launch video_device:="/dev/video1"
此时ros会读取/dev/video1摄像头的数据
标定校准摄像头:
开始标定
rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.024 image:=/usb_cam/image_raw camera:=/usb_cam
出现
把标定板放入画面中
等到XYSIZESKEW 进度条变化
第一个按钮变亮,然后点击第一个按钮,然后等待一段时间,期间这个窗口会变灰色,但是不用关闭窗口,耐心等待。
参数计算之后会恢复,同时终端会显示标定的结果,点击“save"之后,标定文件会出现在默认的文件夹下,并在终端中看到路径。
(以下内容参考于:这个链接)
在用黑白格校准之后,点击“save"之后会在终端出现下图中的最后一行
进入 /tmp/calibrationdata
mv ost.txt ost.ini
rosrun camera_calibration_parsers convert ost.ini head_camera.yaml
mkdir ~/.ros/camera_info
mv head_camera.yaml ~/.ros/camera_info
gedit ~/.ros/camera_info/head_camera.yaml
最后只需要打开head_camera.yaml文件修改一下就能保证该校准参数文件正常使用了:
启动文件
<launch>
<node name="usb_cam" pkg="usb_cam" type="usb_cam_node" output="screen" >
<param name="video_device" value="/dev/video0" />
<param name="image_width" value="1280" />
<param name="image_height" value="720" />
<param name="pixel_format" value="yuyv" />
<param name="camera_frame_id" value="usb_cam"/>
<param name="io_method" value="mmap"/>
//value 现在写的是固定路径,自行修改
<param name="camera_info_url" type="string" value="file:///home/robot/crop_line/src/start_camera/calibration_file/head_camera.yaml" />
</node>
<node name="image_view" pkg="image_view" type="image_view" respawn="false" output="screen">
<remap from="image" to="/usb_cam/image_raw"/>
<param name="autosize" value="true" />
</node>
</launch>
-------------------------------------------------------------------------------------------------------------------------------------------------------
以上内容属于图像预处理部分的第一部分,图像的畸变矫正
-------------------------------------------------------------------------------------------------------------------------------------------------------
图像处理的流程:
先创建一个节点,然后创建一个cv_bridge object,使用cv_bridge将ROS的图像数据转换为opencv的图像格式,frame = self.bridge.imgmsg_to_cv2(ros_image, "bgr8"),然后调用处理函数对图像进行处理display_image = self.process_image(frame),先后显示处理后的结果。
ROS中HSV颜色提取:
代码:
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<ros/ros.h>
using namespace cv;
#include<iostream>
#include<string>
#include<cv_bridge/cv_bridge.h>
#include<image_transport/image_transport.h>
#include <sensor_msgs/Image.h>
#include<sensor_msgs/image_encodings.h>
using namespace std;
//输入图像
Mat img;
//灰度值归一化
Mat bgr;
//HSV图像
Mat hsv;
//色相
int hmin = 0;
int hmin_Max = 360;
int hmax = 180;
int hmax_Max = 180;
//饱和度
int smin = 0;
int smin_Max = 255;
int smax = 255;
int smax_Max = 255;
//亮度
int vmin =0;
int vmin_Max = 255;
int vmax = 255;
int vmax_Max = 255;
//显示原图的窗口
string windowName = "src";
//输出图像的显示窗口
string dstName = "dst";
//输出图像
Mat dst;
//回调函数
void callBack(int, void*)
{
//输出图像分配内存
dst = Mat::zeros(img.size(), img.type());
//掩码
Mat mask;
inRange(hsv, Scalar(hmin, smin, vmin), Scalar(hmax, smax, vmax), mask);
//掩模到原图的转换
for (int r = 0; r < bgr.rows; r++)
{
for (int c = 0; c < bgr.cols; c++)
{
if (mask.at<uchar>(r, c) == 255)
{
dst.at<Vec3b>(r, c) = bgr.at<Vec3b>(r, c);
}
}
}
//输出图像
imshow(dstName, dst);
//保存图像
//dst.convertTo(dst, CV_8UC3, 255.0, 0);
imwrite("HSV_inRange.jpg", dst);
}
void callb(const sensor_msgs::ImageConstPtr& imgPtr)
{
cv_bridge::CvImagePtr cv_ptr;
cv_ptr = cv_bridge::toCvCopy(imgPtr, sensor_msgs::image_encodings::BGR8);
cv_ptr->image.copyTo(img);
imshow("img",img);
bgr = img.clone();
//颜色空间转换
cvtColor(bgr, hsv, CV_BGR2HSV);
//cout << hsv << endl;
//定义输出图像的显示窗口
namedWindow(dstName, WINDOW_GUI_EXPANDED);
callBack(0,0);
waitKey(1);
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "tupian_debug");
ros::NodeHandle nh;
ros::Subscriber imageSub = nh.subscribe("/zed/rgb/image_raw_color",10,callb);
//输入图像
img = imread("/home/wang/Desktop/original_screenshot_14.08.2019.png");
if (!img.data || img.channels() != 3)
return -1;
imshow(windowName, img);
bgr = img.clone();
//颜色空间转换
cvtColor(bgr, hsv, CV_BGR2HSV);
// cout << hsv << endl;
//定义输出图像的显示窗口
namedWindow(dstName, WINDOW_GUI_EXPANDED);
//调节色相 H
createTrackbar("hmin", dstName, &hmin, hmin_Max, callBack);
createTrackbar("hmax", dstName, &hmax, hmax_Max, callBack);
//调节饱和度 S
createTrackbar("smin", dstName, &smin, smin_Max, callBack);
createTrackbar("smax", dstName, &smax, smax_Max, callBack);
//调节亮度 V
createTrackbar("vmin", dstName, &vmin, vmin_Max, callBack);
createTrackbar("vmax", dstName, &vmax, vmax_Max, callBack);
//callBack(0,0);
ros::spin();
return 0;
}
修改:
1、在ROS工作空间修改launch文件以及CMakeLists文件,让其可以运行
2、修改代码中订阅的话题名字,修改为自己相机节点的名字
3、修改“输入图像”图像的位置,换成自己的
4、在调节框中调节所选目标的颜色区域,进行颜色分割,记录H 、S 、V的max以及min值
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
关于cv_bridge 以及 opencv
cv::Mat img; /// Input image in opencv matrix format
cv::Mat img_filt; /// Filtered image in opencv matrix format
int dir; /// Direction message to be published
cv::Mat 是类,它利用了类的特性,将内存管理和数据信息封装在类的内部,用户只需要对Mat类对象进行数据或面向对象操作即可。Mat本质上是由两个数据部分组成的类: (包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等) 的矩阵头和一个指针,指向包含了像素值的矩阵(可根据选择用于存储的方法采用任何维度存储数据)
CvScalar就是一个包含四个元素的结构体变量
cv::inRange 进行阈值处理
下面代码功能是:将一副图像从rgb颜色空间转换到hsv颜色空间,颜色去除白色背景部分
hsv = cv2.cvtColor(rgb_image, cv2.COLOR_BGR2HSV)
然后利用cv2.inRange函数设阈值,去除背景部分
mask = cv2.inRange(hsv, lower_red, upper_red)
函数很简单,参数有三个
第一个参数:hsv指的是原图
第二个参数:lower_red指的是图像中低于这个lower_red的值,图像值变为0
第三个参数:upper_red指的是图像中高于这个upper_red的值,图像值变为0
mask = cv2.inRange(hsv, lower_red, upper_red) #lower20===>0,upper200==>0,lower~upper==>255
就是将低于lower_red和高于upper_red的部分分别变成0,lower_red~upper_red之间的值变成255
给机器人的线速度是在contro_robot.cpp中
感兴趣区域(ROI,region of interest)
机器视觉、图像处理中,从被处理的图像以方框、圆、椭圆、不规则多边形等方式勾勒出需要处理的区域,称为感兴趣区域,ROI。
在Halcon、OpenCV、Matlab等机器视觉软件上常用到各种算子(Operator)和函数来求得感兴趣区域ROI,并进行图像的下一步处理。 在图像处理领域,感兴趣区域(ROI) 是从图像中选择的一个图像区域,这个区域是你的图像分析所关注的重点。圈定该区域以便进行进一步处理。使用ROI圈定你想读的目标,可以减少处理时间,增加精度。
提取三个关键点是:
1、图像的预处理
2、作物行定位点提取
3、作物行直线拟合
关于第一点:
进行灰度处理,看用哪一种灰度算法处理 ,4种常用的灰度化方法:G-R、2G-R-B、ExG-ExR、在HSI空间提取S分量
对于作物识别或杂草的识别最常用的灰度化方法为超绿色法: ExG=2G-R-B
将图像分离通道
std::vector<cv::Mat> rgbChannels(3);
split(img, rgbChannels)
1、split—提取R、B、G分量(返回值顺序为:B、G、R)
图像灰度化,以及以640*480大小显示图像
/* gray image*/
cvtColor(img,dst,CV_BGR2GRAY);
namedWindow("Display gray Image", 0 );
cvResizeWindow("Display gray Image",640,480);
imshow("Display gray Image", dst);
二值化
Otsu
利用形态学的方法的方法去噪,然后Hough变换,然后利用最小二乘法进行直线拟合
形态学
开操作- open
先腐蚀后膨胀
可以去掉小的对象,假设对象是前景色,背景是黑色
闭操作- close
先膨胀后腐蚀(bin2)
可以填充小的洞(fill hole),假设对象是前景色,背景是黑色
以上都是图像的预处理
定位点的提取
基于特征点群的作物定位点提取算法,常用角点检测进行角点检测
SUSAN特征角点(SUSAN算子是一种高效的边缘和角点检测算子,并且具有结构保留的降噪功能)边缘检测
然后最小二乘法
/*
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(close, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //CV_RETR_EXTERNAL只检测外部轮廓,可根据自身需求进行调整
Mat contoursImage(close.rows, close.cols, CV_8U, Scalar(255));
for (int index = 0; index >= 0; index = hierarchy[index][0]) {
cv::Scalar color(rand() & 255, rand() & 255, rand() & 255);
//cv::drawContours(contoursImage, contours, index, Scalar(0), 3, 8, hierarchy);
Rect rect = boundingRect(contours[index]);//检测外轮廓
rectangle(contoursImage, rect, Scalar(0,0,255), 3);//对外轮廓加矩形框
}
//imwrite("/home/robot/pictures/00.jpg", contoursImage);
imshow("contour_Image", contoursImage);
*/
压缩图象
ROS图象<->OPENCV相互转换
这里是一个节点,监听ROS图像信息话题,将图像转换为cv::Mat并在其上画圆,用opencv显示图像。然后图像将重新在ROS上显示。
#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
#include <sensor_msgs/image_encodings.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
//static const std::string OPENCV_WINDOW = "Image window";
class ImageConverter
{
ros::NodeHandle nh_;
image_transport::ImageTransport it_;
image_transport::Subscriber image_sub_;
image_transport::Publisher image_pub_;
public:
ImageConverter()
: it_(nh_)
{
// Subscrive to input video feed and publish output video feed
image_sub_ = it_.subscribe("/camera/rgb/image_raw", 1,&ImageConverter::imageCb, this);
image_pub_ = it_.advertise("/image_converter/output_video", 1);
//cv::namedWindow(OPENCV_WINDOW);
}
~ImageConverter()
{
//cv::destroyWindow(OPENCV_WINDOW);
}
void imageCb(const sensor_msgs::ImageConstPtr& msg)
{
cv_bridge::CvImagePtr cv_ptr;
try
{
cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
}
catch (cv_bridge::Exception& e)
{
ROS_ERROR("cv_bridge exception: %s", e.what());
return;
}
cv::Mat output = detectAndDraw(cv_ptr->image);
cv_ptr->image = output;
// Output modified video stream
image_pub_.publish(cv_ptr->toImageMsg());
cv::waitKey(3);
}
cv::Mat detectAndDraw(Mat& img){
if (img.rows > 60 && img.cols > 60)
cv::circle(img, cv::Point(200, 200), 100, CV_RGB(255,0,0));
// Update GUI Window
//cv::imshow("crop tracker", img);
//cv::imshow(OPENCV_WINDOW, img);
return img;
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "image_converter");
ImageConverter ic;
ros::spin();
return 0;
}
代码 read_img_send.cpp
功能是订阅相机启动节点的图像,发布经过opencv处理后的图像
目前经过改编之后可以实现,先tx2上处理图像,然后把处理之后的结果发布,然后远程端是可以通过订阅话题看到处理之后的结果的
命令:
roslaunch start_camera uvc_camera.launch
rosrun follow_crop read_img_send
心得:要想成功的在远程端运行,有一点是绝对不能写的,就是不能用imshow(),不能有任何的窗口显示
代码删除没有用到的东西
#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
#include <sensor_msgs/image_encodings.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
class ImageConverter
{
ros::NodeHandle nh_;
image_transport::ImageTransport it_;
image_transport::Subscriber image_sub_;
image_transport::Publisher image_pub_;
public:
ImageConverter()
: it_(nh_)
{
image_sub_ = it_.subscribe("/camera/rgb/image_raw", 1,&ImageConverter::imageCb, this);
image_pub_ = it_.advertise("/image_converter/output_video", 1);
}
~ImageConverter()
{
}
void imageCb(const sensor_msgs::ImageConstPtr& msg)
{
cv_bridge::CvImagePtr cv_ptr;
try
{
cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
}
catch (cv_bridge::Exception& e)
{
ROS_ERROR("cv_bridge exception: %s", e.what());
return;
}
cv::Mat output = detectAndDraw(cv_ptr->image);
cv_ptr->image = output;
image_pub_.publish(cv_ptr->toImageMsg());
cv::waitKey(1);
}
cv::Mat detectAndDraw(Mat& img){
if (img.rows > 60 && img.cols > 60)
cv::circle(img, cv::Point(200, 200), 100, CV_RGB(255,0,0));
return img;
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "image_converter");
ImageConverter ic;
ros::spin();
return 0;
}
整合完毕之后,先启动相机,然后启动识别秧苗线的程序,在rqt中查看话题,反馈回来的图象信息
感觉方向信息没有发布成功,之后进行测试。