@ObservedObject basically indicates your view's desire to observe some reference type that conforms to ObservableObject. It's sole purpose is to indicate to SwiftUI that you want your view updated when changes are detected on the object that is being observed.
@StateObject does that as well, but, and this is very important, also controls the lifetime of the @ObservedObject. This means that a property marked with @StateObject will get instantiated and destroyed only when appropriate. @StateObject properties don't get destroyed and re-instantiated every time their containing view struct goes in and out of scope (just like @State). By comparison, @ObservedObject makes no such guarantees.
You can use instances created with @StateObject directly in the view that declares them itself, or pass them down the hierarchy in @ObservedObject properties or in the environment via @EnvironmentObject property and .environmentObject modifier pair.